Poise is supported two ways:
- As a service (
deploy/, Operating Poise): Caddy and the gateway are the only public entry, and each person's Poise runs in its own workspace container behind them. - On a single computer, bound to loopback, as a one-person application.
Never expose a workspace or a single-computer Poise directly. Their capabilities include launching unsandboxed agent processes, modifying files, and acting on GitHub with the person's accounts. They are safe only behind the gateway's identity assertions, or on loopback.
Service architecture is the precise contract. In short:
- People sign in with GitHub at the apex domain. The gateway admits allow-listed logins, members of allowed organisations, and admins. It reads the login and discards the GitHub token.
- A handle stays with the GitHub account id that first claimed it, so a renamed and reclaimed login cannot take over a workspace.
- Each workspace host serves only its owner. Admins get no implicit access, but can disable anyone, which ends that person's sessions and device tokens at once.
- Sessions use host-only,
HttpOnly,Secure,SameSite=Laxcookies. Workspace hosts get their session through single-use tickets bound to the requesting browser (poise_bind), so a ticket minted by someone else cannot sign a visitor into the wrong workspace.
- The gateway strips its own cookies, a device's
Authorizationheader and any client-sent identity header before forwarding. - It adds a fresh 60-second Ed25519 identity assertion naming the owner and a scope.
- Workspaces cannot set cookies: every
Set-Cookiethey send is dropped. - Request bodies are bounded at 32 MiB, and the gateway bounds what it reads back from a workspace. This keeps one person from exhausting the shared gateway.
- The gateway holds the Docker socket, which is root on the host. Run the service on a host dedicated to it, and keep
deploy/.envand the gateway's data volume (with its signing key) readable only by the operator. - Anyone with root on the host can read every workspace volume, including each person's agent CLI logins. People using the service must trust its operator.
- Backups contain the same material.
deploy/backup.shwrites each one into a directory only its owner can open (umask 077). Keep them on storage with the same protection.
- Each person's workspace is a container:
- running as uid 10001 with every capability dropped and
no-new-privileges; - under memory, CPU and process limits;
- on a network shared only with the gateway.
- running as uid 10001 with every capability dropped and
- Isolation between people is that container boundary. Set
POISE_WORKSPACE_RUNTIME=runsc(gVisor) where a stronger boundary is wanted. - Inside a workspace, agents run unsandboxed with the person's own credentials: anything those credentials allow, an agent can do. The browser terminal in Settings → Accounts is a shell as that same user, open only to the owner's browser scope.
- Device tokens are stored hashed. They reach only
/api/link/*, and expire after 30 idle days or 365 days. - A revoked, expired or disabled device gets 401 and pairs again.
- Poise Link accepts only plain trigger/replace string pairs and writes its own rendering of them. Nothing the server sends can become an Espanso shell, script, form or variable.
- Notifications open only http(s) addresses.
The service had adversarial reviews of the gateway (twice), service mode, and the deployment end to end. A final review across the whole system was stopped before it finished. What it confirmed is fixed:
- The relay and read bounds above.
- An unescaped model picker in Chat.
- The workspace home folder's permissions.
The areas it did not finish are listed in the open security follow-up.
With POISE_MODE=service Poise is one person's workspace behind the gateway
and listens beyond loopback inside its container. Every request that does not
come from loopback, the page and its assets included, must carry the gateway's
identity assertion: an EdDSA (Ed25519) JWT verified against the gateway's
public key, issued by poise-gateway for this workspace's audience with the
owner as subject (in any case), at most five seconds in the future, not
expired beyond five seconds of skew, living at most 120 seconds, and with a
scope that reaches the route (browser every route, link only
/api/link/*, admin only /api/service/*). Host must be the public host
and Origin, when sent, exactly the public origin; cross-site API calls are
refused. Anything else is refused before a handler runs. The assertion header
is removed before any check, so Poise never echoes, logs or forwards it, and
no cookie or Authorization header ever stands in for an assertion.
Assertions are not tracked for replay: their short lifetime and the gateway,
which mints one per request, bound that risk. Loopback requests keep the
local rules: whatever runs inside the container is already the owner's.
Isolation between people is the container boundary, as
Service architecture describes.
Report vulnerabilities through the repository's GitHub Security Advisory interface. Do not include credentials, private repository content, agent responses, or local file contents in a public issue.
Keep .env local. Confab credentials are read server-side and are never
embedded in the browser bundle. GitHub authentication is owned by gh. For
issue creation, Poise resolves the selected account's token through gh and
passes it only to that short-lived gh api subprocess; Poise does not persist
or expose the token. Every GitHub action the server and Caller take names its
account — your GitHub account or your agent account from Settings — and none
falls back to gh's active account, so with several accounts signed in nothing
posts as the wrong one; a missing account stops the action. Because upgrades
are otherwise non-destructive, schema initialization explicitly purges the
retired plaintext github_token metadata row while preserving legacy content
tables.
Claude authentication is owned by Claude Code's local credential store. Poise
retains only sanitized in-memory health metadata; it never returns tokens,
account email, organization identifiers, or login output through its API.
Claude-backed subprocesses use a monitored local wrapper that discards
non-allowlisted environment variables, isolates the separate Anthropic profile
store, and consumes caller settings into one overlay where Poise-controlled
provider fields win. It neutralizes API helpers plus Anthropic, AWS, Bedrock,
Mantle, Vertex, Foundry, gateway, socket, and identity-token routes. Immediately
before every model process, the same effective environment must report an exact
Claude.ai/first-party status or the launch fails closed. A durable exponential
per-behavior circuit breaker suppresses repeated model calls and external scans
after failures, and the wrapper disables Claude Code's own provider request
retries. Sanitized breaker state is exposed through /api/health; provider
output is not. The supported path remains the user's Claude.ai Pro or Max
subscription on macOS, Linux, or WSL; native Windows is rejected rather than
falling back to a shell-based wrapper.
This isolation prevents provider-credential fallback, but it cannot disable Anthropic account-level Usage Credits. Users requiring a hard spending cap must disable Usage Credits under Claude account Settings > Usage.
The Chat WebSocket applies the same loopback host and browser-origin boundary as the HTTP API. Requests and output buffering are bounded. Mutating commands use durable receipts, so reconnecting or retrying an uncertain request cannot silently start the same work twice. Transcript events, attachment records and Caller delivery state remain local data.
Checkout locks coordinate Poise instances and compatible Caller writers; they are not a system-wide filesystem lock. Registered worker groups and in-flight Poise file operations must settle before releasing a checkout. Uncertainty keeps it blocked rather than allowing another writer to proceed. Native agents now launch without an OS sandbox in both Chat permission modes. Safe mode is off by default (full access); enabling it retains native risk-based approvals. A working directory is not a filesystem boundary. Poise's own filesystem services use checked checkout paths, bounded reads and atomic writes.
Uploaded files have server-issued, session-owned records. Editor handoffs use separate staged copies and version-checked writeback; conflicts are preserved. Revert requires a trustworthy pre-image and an unchanged post-image. A failed read must never be interpreted as proof that a file did not exist.
Provider credentials are not transferred into the browser or another agent. Claude's existing subscription isolation applies to SDK sessions as well as one-shot work. The other installed CLIs retain their own login state.
New-session creation does not accept a repository, branch or filesystem path
from the browser. Workspace files live under the ignored .poise-chat/
directory inside Poise. Its private Git repository has no remote and never
uses the enclosing source checkout for checkpoints. Symlinked storage roots
and unowned non-empty workspace directories are refused. Existing sessions
retain their original boundaries; no histories or documents are relocated.
Clicking a local Markdown link requests a bounded, read-only text preview. The existing loopback/origin checks and runtime session ownership apply. Paths must resolve inside that session's checkout or a tracked Poise source checkout. The latter is the running installation or the configured checkout for the repository in Poise's own package metadata, with its origin verified; a link cannot name a different repository to authorize. Hidden/private paths, credential-like files, symlink escapes and non-regular files are not served. Reads use a no-follow file descriptor, a 512 KiB byte ceiling and a 5,000-line presentation limit. Contents render as text, never active HTML. Previews do not launch an agent or switch checkouts; they describe the current file only.
Issue reviewers are unattended coding agents with full access: each runs its provider's own CLI without tool restrictions or a sandbox, as the local user, in a fresh checkout of the issue's repository. Unlike a Chat session, the text steering them was written by whoever opened the issue, its sub-issues and their comments. The behavior is off until a repository is ticked, and only issues opened by the trusted authors listed beside it are reviewed; keep that list to accounts you trust. Sub-issues and comments by others remain part of what a reviewer reads.
The checkout is a disposable clone outside ~/dev, made from a local mirror
with the token passed per git command and stored in neither repository; it
is not a filesystem boundary. From a reviewer's shell every same-user
credential is reachable — gh's accounts, SSH keys, provider logins.
Reviewers are told not to post; Caller posts their comments through
github-interface as your agent account and only on the issue and its
sub-issues, but "comments only" is an instruction to the agent, not a limit
it cannot break. When a reviewer exits, its process group is killed, and so
is any process still running in its checkout — including one that detached
into its own session; a process that also left the checkout is not found.
Self-improvement authority is limited to a user-authored Poise change request.
The independently installed controller fixes the repository to the one
package.json names (autonomio/poise), a field a change cannot alter; model
output cannot select a different repository or grant merge authority. A
protected change to release/rollback, validation or authorization machinery,
credentials, destructive migrations, or another package's release
configuration requires separate review.
The controller holds the release credential; it is not forwarded to native agents, candidate builds, or the browser. Exact revision checks and CI precede automatic merge. Retained complete releases, an atomic active pointer and a separate recovery service make software restoration independent of models, GitHub and rebuilds. A rollback hold prevents automatic re-promotion of the rejected release. Current/previous identities refer to the served artifact, not merely a checkout revision.
These are local, same-user processes. Environment isolation and scoped tool permissions are not an operating-system sandbox: native tools, dependencies and candidate code must still be treated as code running as the local user. The workflow does not promise to undo arbitrary data loss, external side effects, or stolen credentials by reverting source code. Persistent data and Caller remain outside the replaceable Poise artifact and retain their existing locations and permissions. Deployment handover waits for admitted work; shutdown of a browser connection is not proof that its file operation ended.
The user can separately enable Auto-merge in a Chat session for requested work
across repositories. The server validates a boolean command, session ownership
and durable replay receipts; agent output cannot toggle the setting. Merge
delegation is independent of tool permissions. Safe mode controls whether
native risk approvals are shown; Auto-merge cannot override Safe mode. In
unrestricted mode, once-only native approvals are recorded as unrestricted,
not forged manual clicks. Questions still require real answers. The shared
instructions require normal repository checks/protections and verified merges;
those are not replaced by a new general-purpose server merge gate. The
Poise-only release controller and its credential scope remain unchanged.
See Chat controls for policy application timing.
Queue commands keep the existing session ownership and request replay checks. Enqueue and its transcript receipt commit together; claiming an item and reserving its open turn are also atomic. Only a recorded, successful turn with settled worker/file operations can advance the tail. Crash recovery never re-executes a claimed uncertain task. Browser reloads do not dispatch tasks. Queued attachments use validated server records and checked content hashes; cross-agent handoffs retain the workspace boundary. An isolated Poise change keeps its checkout exclusive to the release controller until that release settles, then allows queued follow-ups. Queueing neither broadens credentials nor changes the session's explicit Auto-merge delegation.
Memories are user-authored context stored in Poise's private SQLite metadata, not a repository file or an agent-managed native memory store. The same-origin GET/PUT endpoint uses transactional revision checks to prevent silent stale-tab overwrites. Text is preserved verbatim with a 64 KiB UTF-8 limit. Browser drafts are retained on save failures; message dispatch waits for outstanding edits.
Only the runtime supplies the memory appendix, after parsing the original request and validating attachments. Memories do not toggle Auto-merge, select repositories, or independently authorize a self-release. All four native adapters place them after other human-message content. As prompt content they are sent to the selected provider and may remain in its native conversation; clearing memories affects future messages, not historical provider context.
/review resolves its target from the server's own session transcript before
starting a direct review, or at dispatch for a queued review. Browsers cannot
substitute a different session's reply or archive. A model-prefixed task is
reserved once through the existing command-receipt path; model selection does
not grant merge authority or change Safe mode.
The reviewing agent intentionally receives access to the full preceding
conversation, including tool outputs. Generated reply and paginated-history
files are private local staging under ignored .poise-chat/reviews/, created
with restrictive permissions and checked paths while the checkout lease is
held. The visible transcript records the person's original command, not a
second copy of the generated archive. Session deletion removes its review
staging. These are same-user filesystem files, not a separate OS sandbox;
review instructions identify quoted history as evidence rather than new
execution instructions. Credentials retain the existing provider isolation.