73 lines
3.3 KiB
Markdown
73 lines
3.3 KiB
Markdown
# Security model
|
|
|
|
## State-file endpoint security model
|
|
|
|
`GET /api/state-file?path=<p>` serves files from agent state dirs and
|
|
the shared space to authenticated dashboard users (browser, operator).
|
|
Two allow-listed root prefixes are accepted; all other paths are rejected
|
|
before touching the filesystem:
|
|
|
|
- `/var/lib/hyperhive/agents/<n>/state/` — per-agent durable notes
|
|
(canonical host form or the in-container view `/agents/<n>/state/`)
|
|
- `/var/lib/hyperhive/shared/` — shared docs (`/shared/` in-container)
|
|
|
|
`/state/...` without an agent prefix is explicitly *not* accepted — it is
|
|
ambiguous from the host's perspective.
|
|
|
|
Defense-in-depth layers (in order):
|
|
|
|
1. **Allow-list prefix check** — rejects without touching the filesystem
|
|
if the path doesn't match either root.
|
|
2. **No symlinks below the matched root** — each path component is
|
|
checked with `symlink_metadata` before canonicalize. A sub-agent
|
|
that plants `ln -s /other/secret /agents/me/state/peek` can't proxy
|
|
another agent's file through this endpoint (canonicalize would
|
|
happily resolve the symlink to a still-within-allow-list path).
|
|
3. **Canonicalize as belt-and-braces** — resolves `..`/`.` traversal
|
|
and rejects if the result escapes the roots.
|
|
4. **`state/` subdir constraint** — under `AGENTS_ROOT`, the second
|
|
path component must be `state/`. Applied, proposed git repos and
|
|
config dirs are off-limits.
|
|
5. **World-readable check** — file must have `mode & 0o004` set.
|
|
A `0600` file inside `state/` would otherwise be accessible to any
|
|
operator with dashboard access.
|
|
|
|
`scan_validated_paths` (broker-message ingest, linkifier) uses the same
|
|
`resolve_state_path` helper so security rules stay in sync — the
|
|
dashboard renders anchors only for tokens that passed the same checks the
|
|
read endpoint enforces.
|
|
|
|
## Nix builds and credential isolation
|
|
|
|
### 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**:
|
|
|
|
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent
|
|
user `<name>`. nixbld users cannot read it.
|
|
- `$HYPERHIVE_STATE_DIR/forge-token` (= `/agents/<name>/state/forge-token`)
|
|
— written at mode `0600` by `hive-c0re/src/forge.rs` and chowned to the
|
|
per-agent uid:gid by `lifecycle::chown_to_agent`. 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.
|