# Row253 C4 addendum: the supervisor belongs after sudo Draft for Duckworth's static ruling. DESIGN.md remains unchanged. Authority: duckworth-0109/0110, RULING-c4.md at 2c49dd80c, and sage-2307/2312. No repaired product code or new test has run. Product scripts still have the original frozen bytes; this addendum does not claim they satisfy C4. ## Finding and evidence The current tester_capture puts GNU timeout --signal=KILL outside sudo, then kills that timeout's process group. It works for the exec-style sudo fake. sudo(8), **Process model**, says the pty monitor creates a new terminal session and forks the command there. **Signal handling** (called Signals in the local rendering) says SIGKILL cannot be caught and relayed. Therefore an outer group kill does not establish that the query in the new session is killed. Evidence was a static read of the installed sudo(8)/sudoers(5) documentation, then Duckworth's independent ruling against sudo 1.9.17p2. No real sudo, host experiment, live unit or live-port experiment ran. The shared fake below is written but unrun. It models the session boundary, not authentication or a pty. ## Proposed trusted supervisor and runtime trade Recommend a reviewed inline Bash supervisor, run **after the uid drop**, using root-validated fixed system Bash, setsid, sleep and kill/builtins. These are host tools, never binaries or scripts from the selected copy. Resolve system symlinks to their real targets; require executable root-owned ELF files and safe root-owned ancestors, outside homes. Do not use PATH lookup for these supervisor tools. env -i removes BASH_ENV and other injected runtime settings. Add setsid to the documented host dependencies. No new configuration knob. This is a proposal under R1's root-validated system-binary option, pending Duckworth's approval of Bash rather than the example GNU timeout. The exact inline supervisor must be identical in both host scripts, with budgets and command argv passed separately. No helper is imported from the copy. Alternatives weighed: - GNU timeout alone inside sudo fixes the session hop but kills one process group. It misses a grandchild that uses setpgid without setsid. It needs the same owned-session cleanup as the proposed supervisor. - Validated --node with inline builtins can implement the same supervisor. The installer already validates it. Acceptance deliberately reports unsafe --node paths through F9/F10; making that binary a required trusted supervisor would change those existing outcomes or need another pinned runtime. - Fixed root-validated system Python can fork a session, multiplex bounded output and scan /proc cleanly. It is already used for P10, but making it mandatory for the config query changes missing/unsafe Python from a failed P10 observation into a preflight refusal. Preserve that behavior here. ## R1: own a query session, not sudo's group The supervisor starts in sudo's possibly separate session as the stack uid. It forks the query child, then setsid occurs in that child through the fixed system setsid binary. The supervisor itself may already be a group leader, so it must not attempt setsid in its own process. Disable Bash job control; the newly forked child is not a group leader, so setsid need not fork again. The child's PID from $! is the kernel session id. No PID comes from copy output. Run a separate five-second watchdog inside the uid boundary. On expiry, on a supervisor catchable signal, on output refusal, and after normal query exit: 1. Scan numeric /proc/*/stat entries for the owned session id. Parse after the final closing parenthesis, so spaces, parentheses and newlines in comm do not shift the kernel fields. Use the kernel session field, not ancestry. 2. SIGKILL every live PID in that session, including different process groups. Repeat until no running members remain or a fixed 250 ms cleanup budget expires. Never select a process from a different session. 3. Wait for the direct child and the watchdog. Refuse on a cleanup limit or read/parse error that prevents establishing cleanup. Zombies are not running; only direct children can be reaped here. Tests pin that distinction. A five-second deadline concerns the inner query. The root-side deadline is independent. Query bytes are accepted only after success and completed cleanup. The query can never authorize a root kill: its stdout supplies no PID/session. The supervisor runs without root authority and kills only its owned session. ## R2: independent root bound and bounded output Root waits at most seven seconds for the metadata sudo call. Keep GNU timeout outside sudo for this separate purpose. Keep the 4096-byte cap plus one-byte overflow detection. All partial output is discarded on any refusal. Give outer timeout its own operator reason, distinct from inner timeout, child failure, output limit and cleanup refusal. A child-supplied exit value alone must not masquerade as proof the outer timer fired. Capture GNU timeout's trusted verbose signal notice separately, with sudo/helper stderr redirected away from that channel. The root-owned notice file is outside the user's home. Validate the combination of notice and status; no eval or source. Apply the same primitive to the new P10 peer probe, with inner two seconds, outer four seconds, a 128-byte output cap and the existing one-second socket connect timeout. Preserve its fixed system-Python safety check and failed-P10 behavior. All observed stdout still requires successful cleanup and exact pid/uid/gid validation. Neither path sends application bytes to the socket. ## R3: narrowed claim and residual C4 now means: killed at the inner bound, and root never waits past the outer bound. A hostile same-uid query can kill/stop the supervisor or setsid away. A hang in the trusted supervisor has the same outer-bound outcome. In that case root refuses and discards stdout; the separate session may remain. This is the existing DESIGN.md:97-99 same-uid limit, not a new closure claim. No root-side cross-session killer is added. It would require another row. The stalled-supervisor fake must prove outer timing, its own refusal reason and no use of partial stdout. It then explicitly kills its recorded leftover session as fixture cleanup, naming that step as the R3 residual. It must not claim the product cleaned that session. Other normal-supervisor cases assert no running members of the recorded query session remain after the call. ## R4 and F5: headers and remaining legacy calls Both headers will name the inner supervisor, its runtime/tool validation, five/seven-second query bounds and two/four-second peer-probe bounds. The old F3 talk path (currently timeout 330 around AS_USER talk) has the same sudo session-hop issue. Propose recording it as a bounded follow-up, rather than changing it in this slice: it predates the socket metadata/probe helpers, drives a 300-second functional conversation and simultaneous F5 scan, and has existing stdout/stderr diagnostics. It is not part of the new five-second config query. This is an explicit F5 scope choice for Duckworth/Sage to accept or reject, not a claim the old talk timer kills a separate session. Likewise, the other legacy AS_USER diagnostics are not newly supervised by this row. ## Shared fake and next evidence packages/cli/tests/case-sudo-fixture.mjs provides the one process-model seam. Both existing test files import it. Mode exec retains the prior fake. Mode hop keeps a parent, forks through setsid, records kernel PID and SID, and relays TERM/HUP/INT only. It cannot relay KILL. The installer draft case makes the copy ignore TERM/HUP and block; it checks recorded query PIDs and the separate fake session, then explicitly cleans fixture-owned PIDs on failure. This is written but not run, and is expected to expose the old product gap. Before the next whole gate, complete both-mode cases for timeout, TERM/HUP, setpgid grandchild, overflow, and the distinct stalled-supervisor residual. Both host-script paths use this seam. Add changes for absent inner supervisor, absent outer bound, leader-group-only cleanup and unbounded read. The setpgid case must kill the group-only change. Record every restore and raw log. Duckworth reviews this addendum and fake statically first. Product code then implements the approved mechanism. All product changes void prior results under sage-2307/mos-1200. Only Sage's next CLEAR permits a diagnostic or gate run. The next gate is both whole touched files, all checks, CLI/WebUI whole and all ten shell files. Existing RED evidence carries only if applicable product bytes remain equal. No host deployment is authorized by this packet.