diff --git a/CLAUDE.md b/CLAUDE.md index 58be516..7ad43eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,8 @@ 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). +- Credential isolation + sandbox threat model: + **[docs/security.md](docs/security.md)**. ## File map diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000..32c6951 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,33 @@ +# Security model + +## Nix builds and credential isolation (issue #240) + +### Background + +Agent containers bind-mount the host's `nix-daemon` socket. The host daemon may +have `sandbox-fallback = false` (strict NixOS defaults), which causes `nix build` +inside nspawn containers to fail — containers lack kernel user namespaces, so nix +cannot set up its build sandbox. `harness-base.nix` sets `sandbox-fallback = true` +so that builds fall back to unsandboxed execution rather than failing outright. + +### Threat model + +Unsandboxed nix builds run as `nixbld` users (non-root, typically UIDs 30001-30010). +Without sandbox isolation, a build derivation's builder script has read access to +any file in the container that the nixbld user can read. + +**What is NOT exposed**: + +- `/root/.claude/` — mode `0700`, owned by root. nixbld users cannot read it. +- `/state/forge-token` — written at mode `0600` by `hive-c0re/src/forge.rs`. + nixbld users cannot read it. + +**Policy**: all credential files written to agent state directories MUST be mode +`0600` or stricter. Do not create world-readable secret files in agent state dirs. + +### Long-term fix + +The proper fix is to enable user namespaces inside nspawn containers +(`--private-users=inherit` in `EXTRA_NSPAWN_FLAGS`) so nix can set up its real +sandbox and `sandbox-fallback` becomes a true last resort. This requires verifying +bind-mount compatibility with user namespace UID mapping and is tracked as a TODO. diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 7cf31eb..e2d855b 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -353,19 +353,7 @@ # fail inside the container. Enable sandbox-fallback so builds that # can't set up the sandbox (no user-namespaces in nspawn) fall back # to unsandboxed local builds rather than failing outright. - # - # Security note (issue #240): unsandboxed builds run as `nixbld` users - # (non-root) and can read any world-readable file in the container. - # Mitigations in place: - # - /root/.claude (claude session) is mode 700, root-owned - nixbld - # users cannot access it. - # - /state/forge-token (Forgejo API token) is written at mode 0600 by - # hive-c0re/src/forge.rs - nixbld users cannot access it. - # All future credential files written to agent state dirs MUST be mode - # 0600 or stricter. Do NOT create world-readable secret files. - # Long-term fix: enable user namespaces in nspawn containers - # (--private-users=inherit) so the nix sandbox actually works and - # sandbox-fallback becomes a true last resort. + # Security implications: see docs/security.md. nix.settings.sandbox-fallback = true; # `claude-code` is unfree. Each per-agent container's nixosConfiguration