diff --git a/CLAUDE.md b/CLAUDE.md index 21c14c64..04fe6dc2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,8 +10,7 @@ when you need depth on a subsystem. This file is the index. - Operator/agent trust-boundary design: **[docs/boundary.md](docs/boundary.md)** (`area:ops` issues for the deployment/gateway/privsep work). -- Agent trust model (trust boundary, prompt-injection threat model, - capability = accepted risk), credential isolation + sandbox threat model: +- Credential isolation + sandbox threat model: **[docs/security.md](docs/security.md)**. ## Repo map diff --git a/docs/security.md b/docs/security.md index a8b6144b..02853500 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,95 +1,5 @@ # Security model -## Agent trust model - -The sections below document specific mechanisms (the state-file endpoint, -nixbld isolation, privilege separation). This section frames the model they -serve: **what hyperhive defends, what it deliberately does not, and where the -operator is accepting risk.** It emerged from a security discussion on -2026-06-24 (prompted by the `gh` CLI helper work) and is the reference for -"is it safe to give an agent capability X?". - -### The trust boundary is the container, not credential storage - -An agent is **trusted code running inside its own nspawn container**. The -boundary that matters is the container: a sub-agent cannot see the host -netns, another agent's container, or another agent's state dir. Within its -own container the agent is privileged — it has **passwordless `sudo` by -default**. Isolating credentials *from the agent itself* is therefore **not a -goal**: an agent can read its own tokens, its own `/home//.claude`, and -run arbitrary commands as root inside its container. (The narrow exception is -*cross-tenant* leakage — e.g. the unsandboxed-nix-build `0600` token policy -below stops a build's nixbld user reading the agent's own forge token, and the -state-file endpoint stops one agent proxying another's files. Those harden the -boundary; they do not sandbox the agent from itself.) - -The corollary: **don't reason about security as "can the agent be stopped from -touching its credentials". Reason about it as "what is the blast radius if this -agent does the worst possible thing with everything it can reach".** - -### Scoped tokens bound the blast radius - -Each agent gets its own scoped credentials, never shared: - -- **forge token** → that agent's Forgejo account only (its own repos + - collaborator grants; cannot act as another agent or as `core`). -- **matrix token** → that agent's matrix account only. - -So a compromised/confused agent's reach on the forge or matrix is bounded by -*its own* account's scope, not the swarm's. This is the main thing standing -between "one agent does something dumb" and "the whole hive is affected". - -### Threat model: prompt injection → confused deputy - -The realistic adversary **never needs to breach the container**. They supply -**untrusted input the agent reads and acts on**: a poisoned issue or PR -comment, a cloned repo's README/CI, a scraped webpage, a crafted matrix -message. The agent is the trusted, capable party; the *input* is the -untrusted part. A successful injection turns the agent into a **confused -deputy** — it uses its legitimate capabilities (push, comment, deploy, run -shell) on the attacker's behalf. - -Mitigations are therefore about **bounding capability and inserting human -checkpoints**, not about sandboxing the agent from its own tools: - -- **Operator merges, not the agent** — an agent may *push* branches, but a - **human (the operator) merges the PR**, keeping a person in the loop on the - highest-value action. On the **internal forge this is technically enforced, - not just convention**: agents can't create repos (`max_repo_creation = 0`), - so every repo is `core`-created with branch protection **on by default** — - merges restricted to the operators team + a required operators-team approval - (`apply_operator_branch_protection` / the config-repo equivalent) — and an - agent (a write collaborator, not a repo admin) can neither change those - settings nor merge its own PR. It is **not** set up for external VCS (GitHub - etc.), though — there, operator-merge is process + accepted risk, not a - technical control. -- **Approvals** — config changes, schedule additions, and other - blast-radius-y operations route through the operator approval queue - (see [`approvals.md`](approvals.md)). - -### Capability = accepted risk - -Every capability granted to an agent is a risk the operator is **explicitly -accepting**. The rule of thumb: - -> **Don't give an agent access to something you can't afford to lose.** - -If an agent can deploy to prod, you are accepting the risk of a dropped -production database (via injection or plain error). If that's unacceptable, -the answer is *don't grant the capability* — not "grant it and hope the -sandbox holds", because there is no sandbox between an agent and the tools you -handed it. - -### No auto-sandboxing of external tokens - -hyperhive provisions and scopes its **own** per-agent forge + matrix tokens. -It does **not** automatically sandbox or scope **external** credentials -(GitHub PATs, cloud keys, third-party API tokens). The scope of an external -token is **operator-accepted risk**: if you drop a broadly-scoped GitHub token -into an agent's config, that agent has exactly that reach, with no hyperhive -layer narrowing it. Scope external tokens tightly at the source (the external -provider) before handing them over. - ## State-file endpoint security model `GET /api/state-file?path=

` serves files from agent state dirs and