docs: state the unlink mechanism precisely (write bit, not sticky bit)

Both the gateway doc and the tmpfiles comment said "a directory without
the sticky bit lets any user unlink files in it". True of the old 0777,
but it names the wrong lever: write permission on a directory is what
confers the right to unlink its entries, and the sticky bit is only a
restraint on that -- it was never set here, so it is not what 0751
changes. Dropping o=w removes the permission outright.

The fix is unchanged; this is so a future reader doesn't go looking for
a sticky bit that was never there. Caught in review by argus.
This commit is contained in:
atlas 2026-08-04 00:29:58 +02:00 committed by mara
commit c5cd8f2ac4
2 changed files with 15 additions and 8 deletions

View file

@ -107,9 +107,12 @@ now set unconditionally for every agent. The mechanism:
Do **not** reintroduce a chown here: tmpfiles re-applies this entry
on every boot *and* every agent spawn/destroy, so any ownership set
afterwards is reverted the next time any agent changes. The mode is
also load-bearing — a directory without the sticky bit lets any user
unlink files in it, so a world-writable socket dir would let anything
that can reach the path replace an agent's socket with its own.
also load-bearing — 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 is not set here). So a
world-writable socket dir would let anything able to reach the path
replace an agent's socket with its own; `o=--x` removes that
permission outright rather than qualifying it.
3. **Marker gate**. After successful `bind_unix`, the harness drops
`<dir>/hyperhive-socket-bound` next to the socket. c0re's
`agent_sockets::write` filters its JSON map by marker presence —

View file

@ -2430,11 +2430,15 @@ async fn sync_agent_tmpfiles(agents: &[AgentTmpfilesEntry]) -> Result<(String, S
// (dials web.sock, and has all of /run/hive-agent bind-mounted in).
// Both sockets are 0666, so traversal is all they need.
//
// 0751 rather than the historical 0777 is a fix, not a tidy-up: a
// directory without the sticky bit lets *any* user unlink files in it,
// so world-writable here means anything that can reach the path could
// delete an agent's socket, bind its own, and receive that agent's
// todos. Declaring the owner here also ends the tug-of-war with the
// 0751 rather than the historical 0777 is a fix, not a tidy-up:
// write permission on a *directory* is what confers the right to
// unlink its entries, whoever owns them — the sticky bit is the only
// thing that would restrain that, and it was never set here. So the
// old world-writable mode let anything able to reach the path delete
// an agent's socket, bind its own, and receive that agent's todos.
// Dropping `o=w` removes that permission outright rather than
// qualifying it. Declaring the owner here also ends the tug-of-war
// with the
// old ChownSocketDir: `d` re-applies on every sync, so a chown made
// afterwards was reset by the next agent's spawn.
if let (Some(uid), Some(gid)) = (entry.uid, entry.gid) {