Security
Threat model
What Agent Office protects, from whom, and which mechanism is responsible for each defence.
What we protect#
| Asset | Where it lives | Why it matters |
|---|---|---|
| Root credentials | Encrypted secret store in the state directory (AES-256-GCM, key file alongside, both 0600): forge admin token, model credentials, agent forge passwords, session and webhook secrets, and with the bundled Gitea its admin password | Whoever holds them can act as the office |
| The audit record | Ledger in the office database, transcripts in the state directory, anchors in the policy repo | It's how you answer "who did what" |
| Policy | The policy repo's main | It decides who exists and what they may do |
| Each agent's memory | Its vault repo | An agent's instructions and knowledge |
| Company repos | Your forge | The actual work product |
| Spend | Model credential (API key or subscription token) | Money |
Who we defend against#
A misbehaving or manipulated agent#
This is the main threat. An agent can make mistakes, or be steered by something it reads: a mail, a file, a web page within its egress list (prompt injection). We assume it may try anything its shell allows.
| It tries to | Stopped by |
|---|---|
| Grant itself a capability | Ring 1. Policy changes need a human merge. hr_propose_change only opens a PR, and a capability window needs a human's approval. |
Rewrite its own instructions (CLAUDE.md) | Ring 2. Protected file patterns on the vault's main (verify check 3). |
| Read the ledger, transcripts or secrets | Ring 3. Separate OS user, state directory is 0700 (verify check 1). |
| Edit the ledger to hide what it did | The ledger is written only by the office, and the hash chain and anchors make edits detectable (verify check 6). |
| Read or write another agent's vault | Ring 3 on disk (check 2), ring 2 on the forge (check 4). |
| Send data to an outside host | Ring 3 strict network allowlist (check 7), and ring 4 deny rules on WebSearch, and on WebFetch, curl and wget unless it has net.egress. |
| Keep using a token after the run | The forge token is revoked at the end of every run, and the office MCP token only works while the run is running. |
| Use an MCP server the office didn't give it, or add rules through a settings file in its vault | Ring 4. Only the office MCP server, with --strict-mcp-config; no user or project settings are loaded. |
| Spend without limit | With hard_stop: true (the default), admission refuses runs once today's spend reaches budget.usd_per_day, and each run gets the remaining budget as --max-budget-usd. Each run is also capped by max_turns and timeout_minutes. |
| Mail someone it shouldn't | mail_send checks the mail.send recipient list server-side, and denials are ledgered. |
| Keep two agents waking each other forever | The mail loop guard: beyond 3 automatic reply hops, a reply is delivered without starting a run (mail.loop_guard). |
A human with limited access#
Every route and human MCP tool declares one permission. A viewer can't chat, an operator can't read transcripts, and only admins read the ledger. A department lead's role covers only that department's agents. Connecting Claude through the human MCP gives it exactly the connecting human's access.
Someone on the network#
- Forge webhooks are verified with an HMAC-SHA256 signature (
X-Gitea-Signature) over the raw body, keyed with thewebhook.secret. An unsigned or wrongly signed webhook is rejected (401) and does nothing. - MCP endpoints need a bearer token. Agent tokens are per run, stored hashed and valid only while their run is
running. Human tokens are stored hashed, shown once and can be revoked; a disabled human's tokens stop working. - The office server listens on
127.0.0.1;officectl installwrites a Caddy site that serves it over HTTPS (Caddy's automatic TLS). Web forms carry a CSRF token.
A compromised office host#
Out of scope for v0.2. The office process holds the forge admin token and the model credentials, and runs agents through sudo. Anyone who controls the office's OS user, or root, controls the office. See limits.
Design rules that follow#
- Agents propose; deterministic checks decide. No permission decision is made by a model, including the Agent Resources agent.
- Fail closed. No sandbox, no run. No model credential, run refused. An agent that isn't
hired, or isn't known, gets no runs. - Record from outside. Events are recorded by the supervisor reading the harness's output stream, and by the office MCP server, not reported by the agent.
- Never log secrets. Token mints and revocations are ledgered without values, and no secret goes on a command line (argv is readable by every user through
ps).