Skip to content

#566 [security][tools] Bash の書き込み検査が env・sudo・timeout などの前置きや { … } の中の書き込みを認識せず、ワークスペース外へ書き込める(#509 の調査で発見) - #574

Merged
Kewton merged 6 commits into
developfrom
feature/issue-566-security-tools-bash-env-sudo-timeout-509
Oct 2, 2026

Conversation

@Kewton

@Kewton Kewton commented Oct 2, 2026

Copy link
Copy Markdown
Owner

What changed

  • src/tools/bash_write_guard/command_prefix.rs (new): peels command prefixes before choosing the program whose write targets are checked.
    • Process prefixes: env, command, builtin, exec, nohup, sudo/doas/sudoedit, timeout/gtimeout, nice, ionice, stdbuf, time and /usr/bin/time, setsid, caffeinate, arch.
    • Shell syntax: !, {/}, the reserved words, and function NAME.
    • Options that take a value are skipped explicitly.
    • Peeling repeats up to a depth limit of 16.
  • If a prefix cannot be resolved, the remaining words are scanned. The command is rejected when the scan finds a write program or another prefix. This is honest failure: no blanket rejection. Examples of unresolvable prefixes:
    • an unknown option
    • a prefix that changes the working directory or the shell (env -C, --chdir, sudo -s/-i/-D/-R)
    • a chain deeper than the limit
  • env -S / --split-string is rejected in every spelling:
    • short option clusters that contain S (-S, -iS, -vS…)
    • every prefix of --split-string (--s … --split-string)
    • this applies after unresolvable options too, because env re-splits, unquotes and expands that string.
  • Write targets added: sudo -e/sudoedit operands and /usr/bin/time -o FILE.
  • src/tools/bash_write_guard.rs: wires the resolved program into write_targets, confinement_rejection (new rejection reasons), and the shared extraction. has_recognized_mutation and protected_path_mutation use that extraction.
  • tests/issue566_bash_command_prefix_write_targets.rs (new): integration tests through path_confinement_rejection.

Verification

  • Runner verify: 15/15 pass on dbc19a3f (commandmate verify --task).
  • Before/after: the new rejection rows fail on the earlier code and pass with this change. This was checked for each fix round: the original fix, the review fixes B1–B4, R1/R2, and E1–E3.
  • cargo mutants on command_prefix.rs, measured with --jobs 1 re-runs:
    • All survivors were classified.
    • The remaining survivors are equivalent: they reach the same Undecidable/NoExecution result through a later branch.
    • Timeouts are non-terminating loop mutants.
  • Merge-gate review (Claude_Sub):
    • The first review found 4 blockers. Two rechecks found 2 more and then 3 more, all in env -S spellings.
    • All were fixed. The final recheck (H-05) reports 0 blockers.
    • About 330 earlier table rows were re-run: 0 changed from REJECT to ALLOW.
  • Unchanged and still passing: issue567_bash_glob_write_targets, issue428_bash_path_tokens, bash_workspace_confinement.

Notes

Developed with the CommandMate parallel-dev harness (PM: Claude Code, leader: Claude_Dev, workers: Command Code).

🤖 Generated with Claude Code

Kewton and others added 6 commits October 2, 2026 01:13
Bash の書き込み検査が segment の先頭語だけで program を決めていたため、
env・sudo・timeout・nice・予約語などの前置きの後ろの書き込みを
ワークスペース外と判定できなかった(#566)。新しい葉 module
`bash_write_guard/command_prefix.rs` に前置きの表と剥がし方を置き、
不動点まで剥がしてから program を決めるようにした。剥がし方が決まらない形は
残りの語を走査し、書き込み program か別の前置きがあれば拒否に倒す。

判断: `sudoedit` の operand を書き込み先として扱う(Issue 設計の「sudoedit は operand が書き込み先」に対応)
判断: `env -S` の operand は語を空白で分割してから走査する(引用された `tee /tmp/f` を検出するため)
読み替え: 「残りの語の basename を走査」は、決められなくなった前置きの直後以降の語を対象とした
本文に無い指摘: cargo mutants を --jobs 3 で回すと共有 target dir の競合で偽の生存変異が出る(記録した missed の `index - 1` / `offset - 1` は直接再適用すると overflow panic で kill される);report に記録

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
レビューで見つかった前置きの表の抜け 4 件をふさぐ。
- env -S / --split-string / -iS の文字列 operand を取り出して空白で分け、書き込み program か前置きがあれば拒否する
- nohup・builtin は直後の -- を飛ばしてから program を決める
- sudo の -k / --reset-timestamp を「実行しない」の表から flag の表へ移す
- 走査の書き込み program の一覧に sudoedit を足す
テストの不足(env -C / --chdir / sudo -D / env -i -- / exec -- / exec -X、env -S の各形、深さの境目 16/17、command -)も追加。

判断: 設計 5(exec の前置きと shell の前置きの区別)は使う側がないため今回は実装しない(#568 で入れる)
判断: env -S の値は空白分割した各語の basename が書き込み program か前置きのとき拒否に倒す
本文に無い指摘: リーダーが cargo mutants を回すため、ワーカー側では実行していない

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
cargo mutants の生存 34 件のうち、offset・cursor のずれが観測できる形を足す。
値を取る option の後ろに workspace 内の書き込み program を置き、閉じ込め許可・
has_recognized_mutation true・保護 path 検出が path を返す の 3 点を固定する。
unknown option と bare `-` の境目、short_cluster_split_string の境目も直接固定。

本文に無い指摘: `env -Sx tee out.txt` と `env -iS x tee out.txt` は -S の値に書き込みが無いのに拒否される。short_cluster 経路が -S の値を使い切らず continue しないためで、fail-closed の安全側だが設計の「誤拒否を絞る」より厳しい。製品は凍結のため未修正、報告のみ
判断: 352:48・469:13・470:13・529:36・563:32・600:32 は等価変異(到達しない len==1、fall-through も同じ Undecidable、args.get(offset) が常に Some)と判断しテストで殺さない
判断: 312:45・315:25 は指示どおり等価として扱い無視する

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…able operands

R1: 決められない形の走査(unresolvable → contains_write_or_prefix)でも env の -S の値を
取り出す。attached(-Stee)・flag をまとめた -iuX -S…・--split-string=…・-S +次の語の
すべてを対象にし、走査が tee・cp などを見落とさないようにする。
R2: -S の値に " ' \ $ のいずれかが含まれていたら、確かめられない書式として拒否する
(安全側)。含まれなければ今と同じく空白で分けて走査する。

結合テストに R1 拒否 5 行・R2 拒否 7 行・許可 2 行を追加。直す前(eddc6620 相当)では
`env -S'"tee" /tmp/f'` と `env -uX -S'tee /tmp/f'` がいずれも拒否されず失敗し、直した後で
通ることを実測した(二点測定)。

判断: R2 の「確かめられない」判定は " ' \ $ の 4 文字を含む場合とする(指示どおり)
本文に無い指摘: 先に報告した `env -Sx tee out.txt` / `env -iS x tee out.txt` の誤拒否は今回の R1/R2 では変わらない(step_env の short_cluster 経路が値を使い切らず continue しないため)。拒否側で安全

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
… scan

unresolvable_scan_rejects の 2 生存(734:32 の ||→&&、735:50 の index + 1→index * 1)を
殺す行を足す。決められない前置きの後ろで -S / --split-string が独立した語になり、その次の語が
R2 の対象("・'・\・$ を含む)で write の語を含まない形を拒否の表に追加した。次の語を通常の語と
して走査しても tee 等が見つからないため、次語の取り出し経路だけが拒否に到達する。

拒否: X=tee env --unknown -S '${X} /tmp/f' / X=tee env --unknown --split-string '${X} /tmp/f' /
env -C sub -S '${X} /tmp/f' / env --unknown -S 'a\_b'
許可: env --unknown -S(-S が最後の語で値なし)

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
env -S の綴りを 1 つずつ塞ぐ方針をやめ、値を問わず一律に拒否する。H-04 で E1〜E3 の
blocker が出たため。

- step_env: env_requests_split_string で、-S を含む短い option のまとまり(-S・-iS・-vS・
  -0S・-iuXS…)と --split-string の接頭辞(--s…--split-string、= の有無を問わず)を値を見ずに
  Step::Reject にする。値をとる option(-u・-C・-P)が先頭なら残りはその値として扱い対象外
  (-uSOMETHING・-u NAME・-u S)。
- 走査(unresolvable_scan_rejects)は値の取り出しをやめ、語ごとの一律判定と
  contains_write_or_prefix だけにする(env -uX --unknown -S… でも拒否)。
- 新 Resolution::UnverifiableSplitString を追加し、理由を「env -S / --split-string」として
  拒否する(Undecidable とは別扱い)。
- 消した関数: env_split_string_unsafe、short_cluster_split_string。Step::Reject は該当の
  option 語を持ち、bash_write_guard の write_targets/confinement_rejection に配線した。

変えた期待の行:
- 結合 許可→拒否へ移動: env -S'cargo test' / env -S / env --split-string / env --unknown -S /
  env -S x tee out.txt / env --split-string x tee out.txt / env --split-string=x tee out.txt。
  結合 拒否に追加: env -iS '"tee" /tmp/f' / env -vS 'tee\_/tmp/f' / env -0S '${X} /tmp/f' /
  env -S'-Stee /tmp/f' / env -S'--split-string=tee /tmp/f' / env --split='tee /tmp/f' /
  env --s='tee /tmp/f' / env --split-str='cp a.txt /tmp/f' / env -iuXS 'x' /
  env -uX --unknown -S 'cargo test' / sudo env -S'x y' / timeout 5 env -S'x y'。
  結合 許可に追加: env -i PATH=/usr/bin cargo test / env -u HOME cargo test /
  env -uSOMETHING cargo test / env -u S cargo test。
- 単体 resolution_table: env -Scargo test・env -S・env --split-string を no_execution から
  UnverifiableSplitString へ、-S を含む他の行も同じく更新。command_prefix_marks_unresolvable_chains
  の env -S 行も UnverifiableSplitString へ。
- 単体 short_cluster_split_string_table を env_requests_split_string_table に置換。
- bash_write_guard の write_targets table の env -S 行を
  ("-S", UNVERIFIABLE_SPLIT_STRING_OPERATION) へ。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@Kewton
Kewton merged commit 1e193ae into develop Oct 2, 2026
5 checks passed
@Kewton
Kewton deleted the feature/issue-566-security-tools-bash-env-sudo-timeout-509 branch October 2, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant