docs(security): ci netns, knowledge .git tmpfs, matrix id/secret split
Security fixes landed without updating the security-model docs; bring them current as present-tense invariants: - security.md: matrix identities are public / cross-agent-readable via GetAgentMeta while the token stays private; name validation extends to GetAgentMeta; the nix-build blast radius has a network dimension (hive-ci netns) alongside the 0600 token policy. - boundary.md: the core dashboard /api has no app-layer auth — protection is the gateway plus network isolation; stated as a standing invariant. - ci.md: hive-ci runs in its own private netns behind the bridge, reaches the forge via the gateway, and cannot reach host-loopback (dashboard/forge port). - knowledge.md: /knowledge excludes .git via tmpfs so the core token in .git/config isn't readable by agents. Refs #2308, #2310, #2313.
This commit is contained in:
parent
82250b8f59
commit
6c95933775
4 changed files with 39 additions and 2 deletions
|
|
@ -51,6 +51,19 @@ Network isolation is now complete and always on: every agent container
|
|||
runs in a private netns behind the hive bridge. The shared-netns mode
|
||||
was removed. See `docs/network.md`.
|
||||
|
||||
Concretely, the core daemon's dashboard `/api` carries **no
|
||||
application-layer authentication** — operator-authority routes are served
|
||||
unauthenticated at the HTTP layer. Their protection is entirely (a) the
|
||||
gateway, which fronts all operator traffic and is where operator auth lives,
|
||||
and (b) network isolation, which keeps agents — and `hive-ci`'s untrusted PR
|
||||
builds — off host-loopback so nothing can reach `127.0.0.1:<dashboard_port>`
|
||||
directly. This is deliberate given the load-bearing role of network isolation
|
||||
above, but it is a standing invariant: the `/api` must never be bound to a
|
||||
non-loopback address or exposed outside the gateway, and every new
|
||||
operator-authority route inherits that assumption. `hive-ci` is treated like an
|
||||
agent for this purpose — it runs untrusted PR code and is netns-isolated for
|
||||
the same reason.
|
||||
|
||||
The `area:ops` issues followed this sequencing:
|
||||
|
||||
1. **Gateway** — pure ergonomics win, unblocks same-origin (lets the
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Set `services.hyperhive.forge.ci.enable = true` in the host NixOS config. That's
|
|||
|
||||
## Container design
|
||||
|
||||
- **Shared host netns**: container reaches hive-forge at `http://127.0.0.1:<httpPort>` (same as hive-gateway).
|
||||
- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://<forge.domain>` (resolved to the bridge IP via `networking.extraHosts`). It cannot reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. Requires `forge.behindGateway = true`.
|
||||
- **Non-ephemeral**: runner credentials persist across restarts (written to container's stateDir on first registration, reused thereafter).
|
||||
- **Sandbox fallback**: nspawn containers can't create user-namespaces, so nix's sandboxing would always fail. Module sets `nix.settings.sandbox-fallback = true` in the container — nix builds run unsandboxed (safe because the container is already isolated). See `docs/gotchas.md`.
|
||||
- **Credential isolation**: the forge admin token (`forge-core-token`) never enters the container. A host-side oneshot service (`hive-ci-prefetch.service`) performs all forge API calls and writes only the runner registration token into the container via a read-only bind-mount at `/run/hive-ci/runner-token`.
|
||||
|
|
@ -125,7 +125,7 @@ nspawn containers cannot create user-namespaces, so `nix.settings.sandbox-fallba
|
|||
|
||||
A malicious `default.nix` or build script in a PR can therefore:
|
||||
|
||||
- **Make arbitrary network requests** to any address reachable from the container. The container shares host netns, so `http://127.0.0.1:<forgePort>` is reachable with the forge API — without admin credentials, but public/read endpoints are accessible.
|
||||
- **Make arbitrary network requests** to any address reachable from the container. The container runs in its own netns behind the hive bridge, so it reaches the forge only through the gateway (`http://<forge.domain>`, public/read endpoints — no admin credentials) and **cannot** reach host-loopback services: the unauthenticated core dashboard at `127.0.0.1:7000` and the raw forge port are off-limits (bridge→127.0.0.0/8 is dropped).
|
||||
- **Write to the container filesystem**, including corrupting the runner's state dir or `.runner` credentials.
|
||||
|
||||
The core admin token (`forge-core-token`) is **not** bind-mounted into the container. It is accessed only by the host-side `hive-ci-prefetch.service` before the container starts. A build process can still reach forge over the network, but cannot use the admin token to issue privileged API calls.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ before returning.
|
|||
from the host clone on every container start. Gone when container
|
||||
is stopped; reappears on next start with the current clone state.
|
||||
|
||||
The mount deliberately **excludes `.git`**: the host clone embeds the `core`
|
||||
token in `.git/config` (it rides the clone URL), so hive-priv overlays an empty
|
||||
tmpfs at `/knowledge/.git` — agents see the documents, not the repo metadata or
|
||||
token.
|
||||
|
||||
## Contributing
|
||||
|
||||
Agents are read-only collaborators on `internal/knowledge`, so they
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ 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".
|
||||
|
||||
**Identity vs. secret (matrix).** The scoping is on the _secret_, not the
|
||||
_identity_: an agent's matrix **token** is private to its own account, but its
|
||||
matrix **identities** — the public handles (`name`, `user_id` `@user:server`,
|
||||
`homeserver`) — are intentionally readable by any agent via `GetAgentMeta`, so
|
||||
peers can find and address one another on a shared matrix instance. Only the
|
||||
public handle crosses that boundary; the token never does.
|
||||
|
||||
### Threat model: prompt injection → confused deputy
|
||||
|
||||
The realistic adversary **never needs to breach the container**. They supply
|
||||
|
|
@ -127,6 +134,11 @@ Defense-in-depth layers (in order):
|
|||
dashboard renders anchors only for tokens that passed the same checks the
|
||||
read endpoint enforces.
|
||||
|
||||
The same invariant holds wherever an agent-supplied name reaches a filesystem
|
||||
path: the agent socket's `GetAgentMeta` validates `name` with
|
||||
`validate_agent_name` before building `agent_notes_dir(name)`, so a `..`
|
||||
component can't traverse.
|
||||
|
||||
## Nix builds and credential isolation
|
||||
|
||||
### Background
|
||||
|
|
@ -143,6 +155,13 @@ Unsandboxed nix builds run as `nixbld` users (non-root, typically UIDs 30001-300
|
|||
Without sandbox isolation, a build derivation's builder script has read access to
|
||||
any file in the container that the nixbld user can read.
|
||||
|
||||
The blast radius also has a **network** dimension. hive-ci runs its unsandboxed
|
||||
builds of untrusted PR code in its own private netns behind the hive bridge: a
|
||||
build reaches the forge only through the gateway and cannot reach host-loopback
|
||||
services — including the core dashboard at `127.0.0.1:<dashboard_port>`, which
|
||||
has no application-layer auth of its own (see [`docs/ci.md`](ci.md)). The `0600`
|
||||
token policy bounds file reads; network isolation bounds network reach.
|
||||
|
||||
**What is NOT exposed**:
|
||||
|
||||
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent
|
||||
|
|
|
|||
Loading…
Reference in a new issue