diff --git a/docs/boundary.md b/docs/boundary.md index c3ba808c..6f389643 100644 --- a/docs/boundary.md +++ b/docs/boundary.md @@ -5,12 +5,10 @@ _implementation_ work — container network isolation, the unifying gateway, core-daemon privsep — is tracked as `area:ops` issues on the forge. -The operator/agent boundary is now technically enforced, not just a -convention. Containers run in private netns (network isolation is +The operator/agent boundary is technically enforced, not just a +convention: containers run in private netns (network isolation is always on), the gateway proxies all operator-facing traffic, and -`hive-c0re` runs as the unprivileged `hive-core` user. All three -`area:ops` pillars — network isolation, the gateway, and privsep — -are complete and active. +`hive-c0re` runs as the unprivileged `hive-core` user. ## Two principals, two paths @@ -47,9 +45,9 @@ every boundary 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. -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`. +Network isolation is complete and always on: every agent container +runs in a private netns behind the hive bridge, and there is no +shared-netns mode. See `docs/network.md`. Concretely, the core daemon's dashboard `/api` carries **no application-layer authentication** — operator-authority routes are served @@ -64,15 +62,18 @@ 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: +The boundary rests on three layers: -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. **Complete** — - always-on, unconditional; the shared-netns mode was removed. +1. **Gateway** — fronts all surfaces (dashboard + every per-agent UI) + on one origin. An nginx nixos-container proxies per-agent UIs under + `/agent//`, which is what lets the inline-answer POST to + `/answer-question/{id}` go same-origin instead of needing a + cross-origin CORS shim. Pure ergonomics — no behavioural risk on + its own. +2. **Network isolation** — the load-bearing layer: every agent + container runs in a private netns behind the hive bridge, always + on and unconditional. This is what turns the operator/agent split + from an honour-system convention into an enforced boundary. 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 @@ -84,15 +85,14 @@ The `area:ops` issues followed this sequencing: systemd unit. The unit binds `/run/hive/priv.sock` with `SocketGroup=hive-core` and mode `0660` and passes the ready listener to the helper as fd 3 (`LISTEN_FDS`). The helper requires this and -bails if it isn't socket-activated — there is intentionally no -self-bind fallback. +bails if it isn't socket-activated. -Dropping the old fallback removed a dev/prod divergence: when -`hive-priv` bound the socket itself it created the file owned by -root's primary group rather than `hive-core`, so a `hive-core` client -couldn't connect the way the socket unit's `SocketGroup` grant -intends. Requiring socket activation everywhere means dev and prod -take the exact same path and the group grant always holds. +⚠️ There is intentionally no self-bind fallback: if `hive-priv` bound +the socket itself, it would create the file owned by root's primary +group rather than `hive-core`, and a `hive-core` client couldn't +connect the way the socket unit's `SocketGroup` grant intends. +Requiring socket activation everywhere keeps dev and prod on the +exact same path, so the group grant always holds. ### the per-agent socket dir @@ -129,10 +129,9 @@ nginx reaches all of `/run/hive-agent` as a plain host path. Dropping them and the rest of the host. That costs no network isolation: nginx binds the host's `:80`/`:443` and reaches `localhost` upstreams, which a netns would have to be opened up for anyway. -🔑 It does mean nothing *implicitly* scopes the privileged reload verb, -so the scope is explicit: the unit name is hard-coded in `hive-priv` — -see `PrivRequest::ReloadGatewayNginx`. **A caller cannot name the unit, -so the verb cannot be steered at another service.** +🔑 It does mean nothing *implicitly* scopes the privileged reload verb — +see [`docs/security.md`](security.md#hive-c0re-privilege-separation) for +how `PrivRequest::ReloadGatewayNginx`'s containment works. ⚠️ Contrast `/shared`, which *is* sticky world-writable (`1777`): it has many legitimate writers, so sticky is the best available answer there.