Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/4-implementation/trust-boundary/boundary-mechanism.md
iris 30a8f71bb5 docs: the trust-boundary pages keep the operator guidance, the mechanism moves to implementation
boundary.md keeps the model, the design rule, why network isolation
matters and host admin socket access; the hive-priv socket activation and
the per-agent socket directory move to 4-implementation/trust-boundary.
security.md keeps the agent trust model and the path-name rule; the nix
builds mode with its threat model, and the hive-c0re privilege separation,
move to their own pages there. Headings and text move as they were, with
one level shaved off the heading depth. Links, code and nix pointers, and
the docs index follow the moves.
2026-11-06 19:45:11 +01:00

3.8 KiB

Socket mechanics behind the operator/agent boundary

How systemd activates hive-priv and how the per-agent socket directory gets its owner and mode, the two pieces of the boundary described in the operator/agent boundary.

hive-priv socket activation

hive-priv is always socket-activated by the hive-priv.socket 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.

⚠️ Intentionally, no self-bind fallback exists: 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

Three principals that share no group reach /run/hive-agent/<name>/, which is why its mode is what it's:

principal reaches needs
the agent's harness binds + unlinks agent.sock, web.sock owner, rwx
hive-c0re dials agent.sock (todo wakes) traverse
the gateway's nginx dials web.sock traverse

The last two land in "other," so the dir is 0751, owned by the agent's container uid/gid — o=--x is traverse without listing, and both sockets are 0666, which is all a dialer needs.

One mechanism creates it, one sets its owner. Before every start, hive-priv creates the dir when missing (EnsureAgentSocketDir, 0751 root:root, no symlink followed) and leaves an existing one alone. The container's own activation (hive-agent-user-migrate) then chowns it to the agent user and sets 0751. Without a user namespace that uid is the host inode's owner. (A namespaced agent, see "User namespace" in docs/2-handbook/agent-lifecycle/persistence.md, gets the dir from EnsureAgentSocketDir owned by its data owner instead, because the container can't chown through the id map.) Don't add another host-side chown or chmod: two owners of one path revert each other. Until the container activates, the dir is 0751 root: nothing but root can plant a socket in it.

The mode is load-bearing, not cosmetic. Write permission on a directory is what confers the right to unlink its entries, whoever owns them, and the sticky bit is the only thing that would restrain that (it isn't set here). A world-writable socket dir therefore lets anything able to reach the path delete an agent's socket and bind its own. On the host, any non-root process whose /run is writable can do that, such as a login session or dnsmasq. nginx only dials: ProtectSystem=strict makes its view of /run read-only. Dropping o=w removes that permission rather than qualifying it.

⚠️ The gateway's nginx and dnsmasq are host services, next to hive-c0re (see docs/4-implementation/networking/gateway.md) — there is no namespace between them and the rest of the host. That costs no network isolation: nginx binds the host's :80/:443 and reaches localhost upstreams, which would require opening a netns anyway. 🔑 It does mean nothing implicitly scopes the privileged reload verb — see docs/4-implementation/trust-boundary/hive-c0re-privilege.md for how PrivRequest::ReloadGatewayNginx's containment works.

⚠️ This dir has exactly one writer, so it needs no world write at all.