Wires the execution gate into DaemonState::boot, reports it on the wire
and in `hound status`, and reduces the daemon to the two capabilities it
actually needs.
Verified live, gate marked on a scratch tmpfs rather than /:
clean binary ran
EICAR binary execve -> EPERM, "Operation not permitted"
hound status Exec gate: armed on /tmp/hound-live, 2 allowed, 1 blocked
CapPrm/CapEff/CapBnd 0000000000200004 (CAP_SYS_ADMIN | CAP_DAC_READ_SEARCH)
Three ordering bugs found by checking rather than assuming, all of which
returned success while doing nothing:
* Capabilities are per-thread. Dropping them after spawning the reader
and workers reduced only the main thread and left four workers holding
full root — the exact opposite of the intent. The drop now happens
after fanotify_init and the marks, but before any thread exists, so
workers inherit the reduced set.
* PR_CAPBSET_DROP needs CAP_SETPCAP in the effective set, and capset had
already thrown it away. Every bounding-set drop failed EPERM, silently,
leaving a full CapBnd behind a log line claiming otherwise. Bounding
set is now drained first, while the authority to do it still exists.
* Because both of the above looked like successes, drop_to_gate_minimum
now reads CapEff and CapBnd back from /proc/self/status and errors if
they are not what was asked for. A privilege reduction that cannot be
observed has not happened.
Also: `hound status` grew an Exec gate line. timed_out above zero is the
number worth alarming on — it means the watchdog is releasing processes
unscanned and the gate has quietly degraded to advisory.
Gate arming and every failure path now log to stderr, so the journal
records a security-relevant state change instead of only the in-memory
event ring.
86 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>