Repository navigation
Hidden-check commands off argv: stream them in on fd 3 - #38
solvyxtech wants to merge 1 commit into
Conversation
A hidden check's command is a secret the worker must not learn, but spawned through the shell the ordinary way it becomes an argv word and shows up in /proc/<pid>/cmdline (world-readable) and ps. A runs+value check that executes the worker's own deliverable hands that deliverable a process whose command line it can read — even inside a per-check PID namespace, where a child can still read its parent's cmdline. runCommand gains hideCommand: when set (Linux/macOS), the shell is spawned as '<sh> -c eval "$(cat <&3)"' and the real command is streamed in on fd 3, so the only thing in argv is that fixed wrapper. fd 3 rather than stdin so a check's stdin is untouched. Wired for every hidden-check spawn: the bar's runCheck, the mutation builtin's runs, the drafted-check trials in preflightCriteria, and the reference value-check try. Note in withhold.ts that masking is exact-substring only (a convenience for Maat's own records, not the confidentiality boundary). Test: a hidden-command run whose body reads its own and a child's cmdline and asserts the command's token is absent, that it still executes and propagates its exit code, and that stdin is left empty; plus a contrast run proving the ordinary spawn does leak the token. Linux-only (skipped on macOS).
|
Review of #38 against the goal-1 requirements (#40 makes the same change on top of #36). The approach is right, and it's the one #40 uses too. Four gaps:
Your |
|
Superseded by #40 in claude/integration-all@3d1ca16 (#49). The two PRs make the same change and conflict in run.ts, bar.ts and reference.ts. Integration keeps #40's wrapper, which closes fd 3 before the check runs, and its call-site coverage, mission assertions included. Your withhold.ts note on exact-substring masking is carried over, reworded for the fd-3 mechanism. This branch is not merged into integration. |
Keep hidden-check commands out of argv (
/proc/<pid>/cmdline,ps)Fixes the blocking finding confirmed by two reviewers (#27 confidentiality note, #29 privsep review): a hidden check's command is a secret the worker must not learn, but spawned through the shell the ordinary way (
spawn(command, { shell })→<sh> -c "<command>") the command becomes an argv word./proc/<pid>/cmdlineis world-readable andpsshows it to every user, so:cmdline.What changed
runCommand(src/run.ts) gainshideCommand. When set — Linux/macOS only; Windows has no/procand no cheap fd passing, so it falls back to the ordinary spawn — the shell is invoked with a fixed wrapper and the real command is streamed in on an inherited file descriptor:The only thing in any argv is
<sh> -c eval "$(cat <&3)"; the command text never appears.evalof the whole fd-3 text runs it exactly as-cwould (and$0is the shell, as with-c).Why fd 3 and not
sh -son stdin: stdin is left exactly as it was (ignore→/dev/null), so a check that reads stdin is unaffected. Feeding the script on stdin (sh -s) would make a check that doesread/catconsume its own script text — a real behaviour change. fd 3 avoids that entirely.Wired on every hidden-check spawn path on this branch:
src/bar.tsrunCheck— the bar check (hideCommand: check.hidden === true);src/bar.tsmutationCheck— baseline, mutated and negated runs (threadedcheck.hidden);src/criteria.tspreflightCriteria— the drafted-check trials before work (hideCommand: !!opts.stray, the seal-time screen);src/reference.tswriteAndTry— the reference value-check try (hideCommand: true).Non-hidden project checks keep the ordinary spawn, so
psstill shows a person's own check command for debuggability.Masking note (
src/withhold.ts)Added a doc note that
maskTextis exact-substring only (plus a trimmed and a markdown-\|-escaped form): it catches a command echoed verbatim but not one a tool has reformatted (re-quoted, re-indented, line-rewrapped). Masking is a convenience for Maat's own records; the confidentiality boundary is keeping the command off disk during the job, out of argv (this change), and behind a uid the worker cannot cross (--worker-user). I kept the masking logic itself unchanged — a fuzzy/whitespace-normalised pass risks mangling legitimate record text, so I left it as a documented limit rather than widen it blindly.Tests
New Linux-only suite in
test/run.test.ts(skips on macOS, no/proc): a hidden-command run whose body reads its own and a spawned child'scmdlineand asserts the command's unique token is absent from both, that the command still executed (RAN=42), and that the exit code propagates and stdin is left empty; plus a contrast run proving the ordinary spawn does put the token in argv (so the test can actually fail).Results
tsc --noEmit: clean.npm test): 2015 pass, 0 fail (the "Could not report code coverage" line is a pre-existing harness warning, unrelated).Left open / needs the same change when rebased
src/privsep.tsCHECK_INIT(~257-262) andcheckSpec(~525-570) build the check as… /bin/sh -c '… exec "$@"' … sh -c command, so the command is still an argv word under privilege separation. When this lands under Run the worker's tools as another user: --worker-user (builds on #27) #29,checkSpecneeds to adopt the same fd-3 streaming (the wrapper would pass the command on fd 3 after thesetprivdrop). Until then,--worker-user/--check-userdoes not close this leak — see the Run the worker's tools as another user: --worker-user (builds on #27) #29 review.hideCommand: truewhen that PR is rebased onto this.claude/hide-checks(Keep hidden checks off disk during a job; task checks run in a throwaway copy #27) so the diff is just this change; I have not merged it and have not pushed to anyclaude/*branch.No competitor names, no subscription backends, nothing written to fine-tune data.