Skip to content

#645 [security][tools] Bash の作業ディレクトリを変えるオプションの省略形・まとまり・git の全体オプションの後の形を検査する - #654

Merged
Kewton merged 5 commits into
developfrom
feature/issue-645-security-tools-bash-git-637
Oct 10, 2026
Merged

Kewton merged 5 commits into
developfrom
feature/issue-645-security-tools-bash-git-637

Conversation

@Kewton

@Kewton Kewton commented Oct 10, 2026

Copy link
Copy Markdown
Owner

概要

Bash の書き込みガードには、作業ディレクトリを変えるオプション(#637)を検査する処理があります。この処理は、次の 4 つの書き方を見落としていました。

  • 長いオプションの省略形(tar --dir=sub・env --ch sub)
  • まとまりの中で、別のオプションの値に食われた -C(tar -cfC -C sub)
  • git の、値をとる全体オプションの後の -C(git -c k=v -C sub)
  • npm -C(--prefix の短い形)

見落とすと、その後ろの / を含まない相対の読み取りが、ワークスペースの root だけを基準に判定されます。オプションで指定した directory の中に外を指す symlink があると、外を読めてしまいました(R=0)。この PR は、4 つの形を作業ディレクトリの候補に加えます。候補を足すだけで、減らす経路はありません。

変更

  • src/tools/bash_write_guard/option_directory.rs
    • 表にあるプログラムの長いオプションは、空でない接頭辞でも一致します(matches_long)。あいまいな省略形も候補を足す向き、つまり厳しい側になります。
    • - で始まる値は値として記録し、その語をオプションとしても続けて走査します。こうして、隠れた -C を見つけます。-- は対象外です。
    • git の値をとる全体オプション(-c・--git-dir・--work-tree・--namespace・--super-prefix・--config-env・--attr-source)は、値の語を読み飛ばしてから走査を続けます。走査は subcommand で止まります。
    • npm -C を表に加えました。
    • merge 前レビューの blocker の修正(17443e4a):value_step(entry, value) は、git(stop_at_subcommand)のとき常に 2 を返します。git の -C の値の語を、もう一度オプションとして読むことはしません。git 以外のプログラムの式は変えていません。
    • 単体テストを足しました。
  • tests/issue645_bash_option_directory_variants.rs(新規、8 本)
    • 公開されている判定 path_confinement_rejection を、tempdir の fixture に対して呼びます。
    • 拒否になる形:表 1 の形、H-20 の blocker の 4 形(git -C --git-dir -C sub など)、tar -C -- -C sub。
    • 許可のままの形:ワークスペースの中を指す形。
    • 値が変わらない形:表 3(meson -C・unzip -d・git --git-dir=x)と、もともと拒否されていた git の形。
    • ファイルの冒頭に、誤拒否の見積りを書きました。過去のエージェントの Bash コマンド 436 件と、Claude Code の Bash コマンド約 59k 件で、変化した件数は 0 です。
  • 変えたのはこの 2 ファイルだけです(+446 −21)。既存のテストの期待は変えていません。runner.rs・loop_run.rs・baseline も変えていません。

検証

  • 15 ゲート:commandmate verify の 15 ゲートを、HEAD 1ba87389 で実行し、15/15 pass でした。fmt、clippy(-D warnings)、cargo test の全体を含みます。この HEAD には develop d708619a を取り込んであります。
  • 二点測定(新しいテストのファイルだけを、古いコードに置いて実行):
  • TMPDIR=/tmp の結合テスト(CI と同じ条件):次の 4 本がすべて pass しました。
    • issue645(8)
    • issue637(28)
    • issue635(6)
    • issue582(7)
  • cargo mutants(--jobs 1、--timeout を明示):
    • 最初の変更(29 件):caught 19・missed 0・timeout 9・unviable 1。
      • missed が 1 件ありました(git -pc の形)。テストを足して、caught に直しました。
      • timeout の 9 件は、走査の添字が進まず、テストが終わらない変異です。
    • blocker の修正の差分(11 件):caught 6、heavy-lock のメモリ上限での停止 4、missed 1。
    • missed の 1 件(value_step の 244:61 &&→||)は、テストを足して直しました(1ba87389、製品コードは変えていません)。
      • その行に絞って流し直した結果(6 件):caught 5、メモリ上限での停止 1、missed 0。
    • timeout とメモリ上限での停止(heavy-lock、exit 137)は、caught に数えていません。

merge 前レビュー

  • H-20(HEAD 2a98c9fe):blocker 1。
    • 内容:git の -C の値が、値をとる全体オプションの名前のとき(git -C --git-dir -C sub log lf2 など)、続く -C を見落としていました。base では R=1 だった 3 形が R=0 になり、base より緩んでいました。
    • 修正:17443e4a で、git では -C の値の語を続けて走査しないようにしました。テストに 4 形を足しました。
  • H-20b(HEAD 30c370d8):merge 可、blocker 0。
    • blocker の 4 形は R=1 になりました。
    • 2,507 行を比べて、R・A・W・P・S が 1→0 の行は 0、V が 0→1 の行も 0 でした。git 以外のプログラムの行に差はありません。
    • その後の 1ba87389 は、テストだけの commit です(製品コードは変えていません)。

確かめてほしい点

  • git の値をとる全体オプションは、本文の一覧どおり完全一致で扱いました。git の長いオプションには、接頭辞の一致を当てていません。
  • まとまり(-pc)の中の c にも、git の値の読み飛ばしが当たります。git 自身はまとめた全体オプションを受け付けないので、緩む向きの実害はありません(H-20 の後続の候補)。

Refs #645

🤖 Generated with Claude Code

Kewton and others added 5 commits October 10, 2026 16:13
A table program's long option now matches a non-empty prefix (tar --dir),
a `-`-prefixed option value is recorded and scanned again so a hidden `-C`
is found (tar -cfC -C sub), git's value-taking global options skip their
value word so a later `-C` is scanned (git -c k=v -C sub), and `npm -C`
joins the table (Issue #645). The changed shapes become R=1; every other
value is unchanged and the change only adds candidates.

判断: git の値をとる全体のオプションは本文の一覧どおり完全一致で扱い、接頭辞一致は加えていない(本文が名前を列挙しているため)。
判断: 値をとる全体オプションは Entry に欄を足して表し、git 以外は空にした(表を作業ディレクトリの解析の中だけに閉じるため)。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Add a `git -pc k=v -C sub log lf2` read case (Issue #645) to the variant
integration test and the leaf unit test, so a cluster where the
value-taking `-c` is not at position 0 pins that its value word is skipped
and the later `-C` is still read. Product code is unchanged.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
git -C always takes exactly one literal path word. Rescanning its value made
a value spelled like a value-taking global option (`--git-dir`, `-pc`, `-c`)
swallow the following `-C sub`, so `sub/lf2` escaped (R=1 -> R=0). Now git's
value is never rescanned (value_step returns 2 for stop_at_subcommand), while
other table programs keep rescanning `-`-prefixed values (Issue #645).

Tests: four new read rejections in
git_c_value_named_like_a_global_option_still_reads_the_next_c, plus leaf unit
assertions. The pre-fix code was observed to fail these.

判断: リーダーの差し戻しどおり、git の -C の値は値オプションとして読まず常に 2 語進め、git 以外の走査は変えない。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Add a leaf unit case (`values(["tar", "-C", "--", "-C", "sub"])`) and a read
rejection (`tar -C -- -C sub -tf lf2` with `sub/lf2` an outward symlink) for a
non-git table program whose `-C` value is literally `--` (Issue #645). The
value is one path word, so the walk advances two words and still reads the
following `-C sub`. Product code is unchanged; the two cases fail under the
`&&` -> `||` mutation of value_step.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
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