From 28df69286d7f1ae8a0e38336fb2be7ea4724572f Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 16:28:55 +0200 Subject: [PATCH 1/3] docs(security): document hive-priv privilege separation model --- docs/boundary.md | 7 +++++-- docs/security.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docs/boundary.md b/docs/boundary.md index 95a07ebc..ba3f7b2b 100644 --- a/docs/boundary.md +++ b/docs/boundary.md @@ -51,9 +51,12 @@ Suggested sequencing of the `area:ops` issues: 1. **Gateway** first — pure ergonomics win, unblocks same-origin (lets the cross-origin CORS shim on `/answer-question/{id}` go - away), no behavioural risk. + away), no behavioural risk. ✅ **Done** — nginx gateway in front of + all surfaces; per-agent UIs proxied under `/agent//`. 2. **Network isolation** next — the step that makes the boundary real. Everything before it is honour-system. 3. **Privsep** last — defence in depth on the core process itself; valuable independent of the other two, but the - biggest refactor. + biggest refactor. ✅ **Done** — `hive-c0re` dropped from root to + `hive-core` user; `hive-priv` is the narrow root helper. + See [`docs/security.md`](security.md) for the privilege boundary table. diff --git a/docs/security.md b/docs/security.md index d740f066..4c9ead78 100644 --- a/docs/security.md +++ b/docs/security.md @@ -71,3 +71,53 @@ 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. + +## hive-c0re privilege separation + +### Background + +`hive-c0re` runs as the unprivileged system user `hive-core` +(`/var/lib/hyperhive` owned by `hive-core:hive-core`). It cannot +directly invoke `nixos-container`, `journalctl -M`, or `systemctl +-M hive-gateway` — those require root. `hive-priv` fills this gap. + +### hive-priv + +`hive-priv` is a minimal privileged helper that runs as root, socket-activated +at `/run/hive/priv.sock` (mode `0660`, group `hive-core` — only the +`hive-core` user can connect). `hive-c0re` calls it via `priv_client` +for every operation that genuinely requires root. + +**Narrow interface** — `PrivRequest` variants map 1:1 to specific +known operations; there is no arbitrary command pass-through: + +| Operation | What it runs | +|-----------|-------------| +| `StartContainer` / `StopContainer` / `KillContainer` | `nixos-container start/stop/kill ` | +| `CreateContainer` / `UpdateContainer` | `nixos-container create/update --flake ` | +| `DestroyContainer` | `nixos-container destroy ` | +| `ListContainers` | `nixos-container list` | +| `ReadContainerJournal` | `journalctl -M -n [filters...]` | +| `ReloadGateway` | `systemctl -M hive-gateway reload/start/reset-failed nginx` | +| `AddBindMount` / `RemoveBindMount` | `systemctl set-property` or `machinectl bind` | +| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state | +| `SetNetworkIsolation` | manage per-container network isolation flags | + +**Container allowlist** — every request is validated against an +allowlist before any operation: only names matching `h-` (the +standard agent prefix), the manager container, or the known sibling +service containers (`hive-gateway`, `hive-forge`, `hive-matrix`, +`hive-ci`) are accepted. Arbitrary container names are rejected. + +**Socket-activated** — systemd starts `hive-priv` on the first +incoming connection (`LISTEN_FDS=1`); it is not running between calls. +The `ProtectSystem=strict` + `ReadWritePaths` sandbox limits filesystem +writes to only the paths `hive-priv` legitimately needs. + +### Privilege boundary summary + +| Component | Runs as | Privilege needed for | +|-----------|---------|----------------------| +| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals | +| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes | +| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving | From a960dcf91a2fc7b2bb36e2c5be228b31546bdb73 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 17:02:42 +0200 Subject: [PATCH 2/3] docs(boundary): rewrite sequencing list as prose, drop checklist markers --- docs/boundary.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/boundary.md b/docs/boundary.md index ba3f7b2b..fe93de85 100644 --- a/docs/boundary.md +++ b/docs/boundary.md @@ -47,16 +47,15 @@ claim above is aspirational. Network isolation is what makes the boundary *real*; the gateway and privsep are ergonomics and defence-in-depth layered on top. -Suggested sequencing of the `area:ops` issues: +The `area:ops` issues followed this sequencing: -1. **Gateway** first — pure ergonomics win, unblocks same-origin - (lets the cross-origin CORS shim on `/answer-question/{id}` go - away), no behavioural risk. ✅ **Done** — nginx gateway in front of - all surfaces; per-agent UIs proxied under `/agent//`. -2. **Network isolation** next — the step that makes the boundary - real. Everything before it is honour-system. -3. **Privsep** last — defence in depth on the core process - itself; valuable independent of the other two, but the - biggest refactor. ✅ **Done** — `hive-c0re` dropped from root to - `hive-core` user; `hive-priv` is the narrow root helper. - See [`docs/security.md`](security.md) for the privilege boundary table. +1. **Gateway** — pure ergonomics win, unblocks same-origin (lets the + cross-origin CORS shim on `/answer-question/{id}` go away), no + behavioural risk. An nginx nixos-container now sits in front of all + surfaces; per-agent UIs are proxied under `/agent//`. +2. **Network isolation** — the load-bearing step that turns the + honour-system split into an enforced boundary. In progress. +3. **Privsep** — defence in depth on the core process; `hive-c0re` + runs as the unprivileged `hive-core` user and delegates root + operations to `hive-priv`, a narrow socket-activated helper. See + [`docs/security.md`](security.md) for the privilege boundary table. From 09e2ad326c1617930f9a0b07047fd4e4a32ee24b Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 18:36:39 +0200 Subject: [PATCH 3/3] fix: update PrivRequest table to match actual hive-sh4re::priv_proto variants Replace stale AddBindMount/RemoveBindMount/SetNetworkIsolation/ReloadGateway entries with the actual current variants from hive-sh4re/src/priv_proto.rs: - WriteNspawnFlags (replaces AddBindMount/RemoveBindMount/SetNetworkIsolation) - WriteResourceLimits (new) - RemoveServiceDropin (new) - DaemonReload (new) - ChownSocketDir / ChmodSocketDir (new) - RunForgeAdmin (new) - RestartMatrixDaemon (new) - ReloadGatewayNginx (renamed from ReloadGateway) --- docs/security.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/security.md b/docs/security.md index 4c9ead78..cebfb621 100644 --- a/docs/security.md +++ b/docs/security.md @@ -98,10 +98,15 @@ known operations; there is no arbitrary command pass-through: | `DestroyContainer` | `nixos-container destroy ` | | `ListContainers` | `nixos-container list` | | `ReadContainerJournal` | `journalctl -M -n [filters...]` | -| `ReloadGateway` | `systemctl -M hive-gateway reload/start/reset-failed nginx` | -| `AddBindMount` / `RemoveBindMount` | `systemctl set-property` or `machinectl bind` | -| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state | -| `SetNetworkIsolation` | manage per-container network isolation flags | +| `ReloadGatewayNginx` | `systemctl -M hive-gateway reload/start/reset-failed nginx` | +| `WriteNspawnFlags` | write `/etc/nixos-containers/.conf` (bind-mount list + network isolation vars) | +| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=` systemd drop-in for agent container | +| `RemoveServiceDropin` | remove `container@.service.d/` drop-in on destroy | +| `DaemonReload` | `systemctl daemon-reload` | +| `ChownSocketDir` / `ChmodSocketDir` | chown/chmod `/run/hive-agent//` socket directory | +| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin ` | +| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state dir | +| `RestartMatrixDaemon` | `systemctl --machine=h- restart hive-matrix-daemon.service` | **Container allowlist** — every request is validated against an allowlist before any operation: only names matching `h-` (the