# hive-priv The minimal **root privileged-helper** for hive-c0re. It runs as root and exposes a narrow unix socket at `/run/hive/priv.sock` that accepts `PrivRequest` JSON lines and performs only the handful of operations that genuinely require root — bind-mount edits, `nsenter` into a container, btrfs subvolume ops. All coordination logic (broker, HTTP, scheduling) stays in the _unprivileged_ `hive-c0re` process, which delegates here. ## Why it exists Privsep. `hive-c0re` runs as the unprivileged `hive-core` user so a bug or a prompt-injection in the large daemon can't directly wield root. The few root operations it needs are funnelled through this small, auditable helper instead. See `docs/trust-boundary/boundary.md` and `docs/trust-boundary/security.md` for the privilege boundary. ## Security model - **Strict allowlist.** hive-priv checks every request against a container-name allowlist before any filesystem or process operation, and accepts only names matching the hive convention (`h-*`, the manager container included). - **No pass-through.** Every `PrivRequest` variant maps to a single known operation; there is no arbitrary-command escape hatch. - **Socket-activated, always.** systemd binds `/run/hive/priv.sock` (`SocketGroup=hive-core`, `0660`) and passes the listener as fd 3 (`LISTEN_FDS`); the helper requires this and has no self-bind fallback, so dev and prod take the identical path and the group grant always holds. The wire contract (`PrivRequest` / response types) lives in the separate `hive-priv-sock` crate so this root binary depends on just the protocol shapes, not the whole daemon-shared crate. ## Layout `src/main.rs` holds the socket loop and the dispatch table, one arm per `PrivRequest` variant. Each op family, with its validation, has its own module: `container` (lifecycle), `container_config` (nspawn flags, systemd drop-ins), `storage` (btrfs subvolumes, quotas, snapshots, disk measurement), `files` (pause marker, socket dir, legacy tmpfiles) and `infra` (gateway nginx, forge admin, infra containers). Helpers more than one family calls live in `shared`. Each module's `//!` header lists its ops. ## Implementation notes ### Container toplevels (create/update) `container_toplevel_action` (in `src/container.rs`) applies a closure that is already in the store; hive-priv builds nothing. hive-c0re names the closure (`toplevel` on `CreateContainer` / `UpdateContainer`), downloaded beforehand with `RealiseClosure`. hive-priv remembers, per agent, the path its last successful `RealiseClosure` returned (in memory), and applies only that path to that agent. It refuses a path never realised for the agent, another agent's and the host's own, and logs an error naming the agent and both paths. A restart between the download and the apply empties the record, so the apply fails and the job retries. hive-priv does not re-check the signature at apply time: `RealiseClosure` enforces it, and the binding limits an apply to what that call stored. `create` passes the path to `nixos-container create --system-path`, so `nixos-container`'s own `buildFlake()` never runs. Without the flag, it builds to a hardcoded relative `.tmp` out-link: `$systemPath` stays `undef` and `"$systemPath.tmp"` interpolates to the bare string `.tmp` in the caller's cwd. With `services.hyperhive.deploy.hive-controller.buildSlots > 1`, two concurrent calls could share that one `.tmp`, and one agent's container got another agent's closure. `update` points the container's profile at the path with `nix-env --set` and reloads the unit if it runs, because `nixos-container update` dies on its version probe before it honors `--system-path`.