Repository navigation
Guard leak-path fix: read timeout's -- and post-duration options as uutils coreutils does - #685
Conversation
… uutils does The command guard took the word right after timeout's duration for the command. GNU timeout runs that word (a `--` there exits 127), but uutils timeout, the coreutils of Ubuntu 26.04, still reads options and an end-of-options `--` after the duration (clap trailing_var_arg, timeout.rs:177 at 0.8.0, :171 at 0.10.0, :173 at 0.12.0). So `timeout 5 -- CMD` and `timeout 5 -s KILL CMD` started a command the guard never examined. Both word readings now step over those options and the separator. The current reading also takes unambiguous long-option prefixes of timeout, nice, stdbuf and env (getopt_long; clap infer_long_args), env's value options of both implementations (-a/--argv0, -f/--file, --env0-from) and clusters a value letter ends, and the uutils multi-call binary (`coreutils UTIL`). The prior reading keeps c26800f's env table, so no command refused before passes. Failing-first: 29 of the 31 new blocked rows failed at f77a35e; all pass now. A replay of 6,582 commands against the base guard found 0 loosened, 0 reason changes and 0 documented commands newly refused. Co-Authored-By: Claude Opus 5.5 <[email protected]>
adoption/hooks/claude/SHA256SUMS pins the guard's new sha256, so the profile installer and the client-configuration check accept the changed guard. manifests/evidence.json carries the new hashes of the guard, its tests, the secret-storage decision and SHA256SUMS. Registry edits are in the branch's last commit (hot-file rule). Co-Authored-By: Claude Opus 5.5 <[email protected]>
…et guard Preserve GNU hyphen-leading command paths in both timeout readings. Expand literal env split strings with trailing argv, and recognize both GNU single-binary dispatch forms. Restore harmless split-command controls and retain failing-first and scoped differential evidence. Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
…le protocol, last commit) Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
Read GNU split-string argv and uutils preprocessing independently. Refuse inferred split options whose payload uutils discards, follow literal split prefixes after assignment operands, and consume generated split options without recursively pre-expanding them. Preserve the raw env no-command backstop and follow GNU coreutils dispatch in the independent reading. Turn the invalid --split allow controls into refusals, retain the harmless separate -vS control, and give the previously masked test a trailing placeholder operand. Correct the decision's GNU/uutils grammar claims and record both proven mistakes in the repository anti-pattern log. Content files: scripts/hooks/secret_path_guard.py, tests/test_secret_path_guard.py, docs/decisions/2026-09-24-secret-storage.md, docs/harness-defaults.md. Validation with CI=true and the requested TMPDIR: - 95 regression subtests fail at a7888d3 and pass after this repair. - python3 -m unittest tests.test_secret_path_guard: 89 tests, 3 skips, passed. - python3 scripts/validate.py: passed. - Regenerated replay: 3,257 commands; 232 newly refused against f77a35e, 95 against a7888d3, zero loosened, reason changes, or errors against both. - git diff --check: passed. The requested temporary directory is absent; Python used its /tmp fallback. Sources: GNU coreutils v9.12 src/env.c and uutils coreutils 0.10.0/0.12.0 src/uu/env/src/env.rs, with the supplied 0.8.0 source comparison. Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
Update adoption/hooks/claude/SHA256SUMS for the repaired guard and re-register the guard, tests, decision, anti-pattern log and pin through scripts/host_receipts.py register_file. Preserve all other registry rows. Keep only adoption/hooks/claude/SHA256SUMS and manifests/evidence.json in this final commit, after the content commit, per docs/lanes.md. Guard SHA256: 9a12a0a0d036b3c6c84cbb52ec4149c47265f091c87da30168bb73c3dc757f5d Validation: CI=true python3 scripts/validate.py passed with 69 components, 9,864 hashed files, 4 profiles and 195 receipts; git diff --check passed. These are integrity and scope checks, with no live provider or GPU run. Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ilt from main plus this PR's files Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round two allowed commands that main or round one refused by replacing GNU env expansion and the historical command tail with a uutils reading. Keep the raw, GNU-expanded and uutils-preprocessed candidates in a bounded work list, and apply ENV_ARG_OPTIONS to every argv. Include inferred long-option value widths. Before coreutils dispatch continues, queue any keyring command found in its raw words so consumed option/script operands retain protection. Add R1, R2, R3, R4, R5 and R5b under the existing prefix loops. Turn the supplied oracle into a permanent comparison against immutable, hash-pinned main and round-one fixtures, including existing rows and launcher variants. Correct the guard comments, decision record and anti-pattern log. Validation with TMPDIR=/tmp/guard685-r3 and CI=true: - Four selected methods fail at ec382e2 with 92 assertions and 2 existing work-budget errors; the repaired methods pass. - python3 -m unittest tests.test_secret_path_guard: 90 tests, 3 skips, passed. - Supplied monotonicity_probe.py: all 18 working cases refused, no errors. - Permanent baseline comparison: 5,149 existing rows and variants. - Regenerated replay: 5,452 commands; 271 newly refused against f77a35e, 95 against a7888d3, zero loosened, reason changes or errors against both. - python3 scripts/validate.py and git diff --check: passed. Sources: GNU coreutils v9.12 src/env.c and src/coreutils.c; uutils coreutils 0.10.0/0.12.0 src/uu/env/src/env.rs; the supplied Sonnet allow-path oracle; this repository's f77a35e and a7888d3 guard snapshots. Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
Update adoption/hooks/claude/SHA256SUMS for the repaired guard and re-register the changed guard, tests, decision, anti-pattern log, integrity pin and four baseline/oracle fixture files through host_receipts.register_file. Preserve all other manifest data. Keep only adoption/hooks/claude/SHA256SUMS and manifests/evidence.json in this final commit after the content commit, following docs/lanes.md. Guard SHA256: 015b8c6e1ccec2df8232465cc9c0f0e40244d97ee0fa892c8a95faac816e4bf5 Validation: python3 scripts/validate.py passed with 69 components, 9,886 hashed files, 4 profiles and 195 receipts; git diff --check passed. Integrity and scope evidence only; no live provider or GPU run. Co-Authored-By: GPT-6.1 Sol (OpenAI Codex) via the packaged SDK worker Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Claude session native-agent-stack-5f: landing at head Observed main Required checks at this head: 8 pass . Unresolved review threads: 0. |
|
Claude session native-agent-stack-5f: post-merge observation. Landed as |
Scope
scripts/hooks/secret_path_guard.py).timeout's duration for the command. uutilstimeout, the coreutils of Ubuntu 26.04, still reads an end-of-options--and options at that position and runs the command after them, so the guard never examined that command.f77a35eb2bf30bc4bf6f3b4ee51bc9ce5397b4c5lane:foundation. Re-registering hashes inmanifests/evidence.jsondoes not make it shared (docs/lanes.md, "Labels and PRs").scripts/hooks/secret_path_guard.pyandtests/test_secret_path_guard.py;docs/decisions/2026-09-24-secret-storage.md(append-only dated addendum);adoption/hooks/claude/SHA256SUMS(the guard's install pin, without which the profile installer refuses the changed guard);manifests/evidence.json(hash re-registration, in the last commit).SOTA sources
All sources were read on 2026-10-04 (UTC).
uutils coreutils (https://github.com/uutils/coreutils), at tags
0.8.0(commitc4093734e2eb),0.10.0(28b6856d7b21) and0.12.0(dc1efd89948a):src/uu/timeout/src/timeout.rsuu_app:.trailing_var_arg(true)is at line 177 (0.8.0), 171 (0.10.0) and 173 (0.12.0), with.infer_long_args(true)on the next line. See https://github.com/uutils/coreutils/blob/0.8.0/src/uu/timeout/src/timeout.rs#L165-L178.uu_appinsrc/uu/env/src/env.rs(-a/--argv0,-f/--file,-C,-S,-u),nice.rs,stdbuf.rsandnohup.rs, at 0.8.0. env was also read at 0.12.0.src/bin/coreutils.rsmainat 0.8.0: a binary named*utilstakes the utility from its next argument.src/common/validation.rsbinary_pathat 0.8.0: on Linux the executed path names the utility, not argv[0].GNU coreutils (mirror https://github.com/coreutils/coreutils of
git.savannah.gnu.org/git/coreutils.git), atv9.7(8e075ff8ee11) andv9.12(c0f8514d9891):src/timeout.c:getopt_long (argc, argv, "+fk:ps:v", long_options, ...)and itslong_options.src/env.c: shortopts"+a:C:iS:u:v0", and longopts withargv0, plusenv0-fromat v9.12.src/nice.c,src/stdbuf.candsrc/nohup.c.doc/coreutils.texiat v9.12:timeout [option]… duration command [arg]…, and "Options must precede operands";glibc manual (mirror https://github.com/bminor/glibc), at
glibc-2.42:manual/startup.texiArgument Syntax: "The argument--terminates all options", and "Users can abbreviate the option names as long as the abbreviations are unique".manual/getopt.texi: a leading+in the option string stops option processing at the first non-option. "The option name may be abbreviated as long as the abbreviation is unique."Ubuntu packaging: the resolute file list of
rust-coreutils0.8.0-0ubuntu3, which ships/usr/bin/coreutils: https://packages.ubuntu.com/resolute/amd64/rust-coreutils/filelistsudo-rs (https://github.com/trifectatechfoundation/sudo-rs), at
v0.2.15:src/sudo/cli/mod.rsnormalize_argumentsmatches long options exactly and ends options at--. Audited; no change needed.uutils 0.8.0 release binary:
coreutils-0.8.0-x86_64-unknown-linux-gnu.tar.gz, sha256ea8f0a4f6815c1da…, equal to the digest the release page publishes. It was run for the measurement below.Evidence-class table
f77a35ebthe guard allowstimeout 5 -- printenv APCA_API_SECRET_KEY,timeout 5 -- env,timeout 5s -- gdb -p 1,timeout --signal=TERM 5 -- printenv …and 25 more launcher forms that run a refused commandlocal_integration, failing firsttimeout 5 -- ls…) allowedlocal_integrationtimeoutreads options and--after the duration and takes long-option prefixes; GNU stops options at the durationsource_reviewtimeout.rs:177(0.8.0),:171(0.10.0),:173(0.12.0); GNUtimeout.c"+fk:ps:v";coreutils.texi"Options must precede operands"timeout 5 -- CMD,timeout -k 2 5 -- CMD,timeout 5 -s KILL CMD,timeout 5 -v CMD,env -a NAME CMDandenv -f FILE CMD, where GNU 9.4 exits 127 or 125local_integration(upstream release binary run locally withechoonly; output quoted here; no receipt)--sig,--kill,--adj,--out,--unandenv -vu NAME CMD; both exit 127 fortimeout -- 5 -- CMDandtimeout 5 -- -- CMD; an argv0 override does not choose the multi-call utility on Linuxlocal_integrationtimeout 5 -- CMDand GNU 9.7 exits 127local_integration(differential replay)local_integrationReplay summary: 6,582 distinct commands were checked against the base guard.
printenv,env,gdborcat .env), 0 loosened, 0 reason changes, 0 errors.Installer dry run:
install_claude_profile.py --home <empty scratch> --only guard --hook secret_path_guard.py --dry-runreported "would install … (sha256 c6b8cfdc073d…)" and wrote nothing (exit 0).Local commands run
All runs used
TMPDIR=<scratch> nice -n 19.Wrapper audit
--handlingtimeout--ends options for both. After it, GNU runs--(exit 127) and uutils skips ittrailing_var_arg); both take long-option prefixesnice--adj; uutils' legacy-Nrewrite matches GNU'sstdbuf--out,--onohup--help/--versionenv-f/--file;-a/--argv0in uutils and GNU ≥ 9.7;--env0-fromin GNU 9.12; clusters (-vu NAME); prefixescoreutils(multi-call)/usr/bin/coreutilson Ubuntu 26.04coreutils UTILis read asUTILsudo--ends optionsdoas,command,builtin,exec,time,setsid,ionice,xargs,chronic, systemd launchersDecision record
docs/decisions/2026-09-24-secret-storage.md, "Addendum 2026-10-04: launcher grammar of GNU and uutils coreutils". It covers the decision, sources, measurement, alternatives rejected, overturn conditions, open items and evidence class.Residuals
env -S STRINGfollowed by more words (env -S 'CMD …' more): the split string becomes the command line. This is a different class (a string-splitting launcher) and is not fixed here.coreutils --coreutils-prog=NAME, and other multi-call binaries (busybox, toybox), are not read.examples/claude-native/workflows/child-usage.mjs(lane counting, not credential protection) readstimeoutthe GNU way, so on a uutils host it would not count the lane intimeout 5 -- LANE. It is outside this PR's paths, and its oracle passes on GNU hosts.timeout -- 5 -- CMDis refused, although both implementations run--there and exit 127. It errs toward refusal and no documented command uses it.Host reinstall after merge (coordinator)
Host evidence
Not applicable: no file under
evidence/hosts/changes.Checklist
version comment (no floating tags). (None changed.)
permissions: contents: read(or a narrower, explicitly justified addition). (None changed.)
added without a documented owner. Test strings use placeholder names only.
moved or overwritten).
🤖 Generated with Claude Code