# 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](../../2-handbook/trust-boundary/boundary.md). ## 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//`, 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`](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.