Repository navigation
[security][tools] Bash の cd・pushd の行き先が 1 つに一致する glob だと作業ディレクトリが検査されず、外を読み書きできる(#613 のレビューで発見) #635
Description
背景
#613 の merge 前レビュー(H-10、2026-10-09)で見つけた、Bash の閉じ込めの検査の既存の見逃しです。#613 の前の develop(0ccc81bd)にもあり、#613 で緩んだものではありません。着手前調査(H-11、2026-10-09)で、#613 の merge 後の develop(2a078deb)でも同じ値であることを確かめ、設計を案 1 に決めました。どちらの調査も、判定の関数を tempdir の fixture に対して呼び、結果を見ただけです。shell は起動しておらず、ワークスペースの外の実際の読み書きは試していません。
問題
cd・pushd の行き先が glob・brace の文字(*・?・[・]・{・})を含むと、作業ディレクトリの候補にも「決められない」の印にもならず、その後のコマンドの相対パスが検査されません。行き先のディレクトリの中にある外向き symlink を通して、外を読め、外に書けます。引用やエスケープで字どおりにした括弧(cd "s[2]"、Next.js の動的ルートのようなディレクトリ)も同じ扱いで、検査されません。
R:2 段目の閉じ込めの判定、A:書き込みと認識、W:書き込み側の拒否、P:保護 path の書き込みの検出、V:bash:verify の自動許可。fixture:ワークスペースの中の s2 は普通のディレクトリで、その中の lf4 は外のファイルを指す symlink。s[2] も字どおりの名前のディレクトリで、中に外向きの lf5 があります。
| 入力(例) | develop の値 | 期待 |
|---|---|---|
cd s2* && cat lf4、cd s? && cat lf4、cd s[2] && cat lf4 |
R=0 | R=1 |
cd {s2,x} && cat lf4、cd {s2,x}; cat lf4、cd s{2,} ; cat lf4 |
R=0 | R=1 |
cd -P s2* && cat lf4、cd -L s2* && cat lf4、cd -- s2* && cat lf4 |
R=0・V=1 | R=1 |
cd s2* && cd . && cat lf4、cd sub && cd de* && cat lf3、cd su? && cd deep && cat lf3 |
R=0 | R=1 |
pushd s2* && cat lf4、pushd {s2,x} && cat lf4 |
R=0・V=1 | R=1 |
(cd s2* && cat lf4)、{ cd s2*; cat lf4; }、if cd s2*; then cat lf4; fi |
R=0・V=1 | R=1 |
cd s2* の後に改行・;・|| で cat lf4 |
R=0 | R=1 |
cd "s[2]" && cat lf5、cd 's[2]' && cat lf5、cd s\[2\] && cat lf5 |
R=0 | R=1 |
cd s2* && echo x > lf4、cd s2* && tee lf4、cd s2* && cp f lf4、cd s2* && touch lf4 |
A=1・W=0 | W=1 |
保護ファイルのあるディレクトリへ glob で cd して、その保護ファイルを rm・tee・> で書く |
A=1・W=0・P=0 | W=1 |
比較:2 つ以上に一致する glob の行き先(cd s* && …)と、$・~・..・- の行き先は、今も拒否されます。heredoc の本文の中の cd(bash <<EOF の中)は shell の中の shell の形(#502)で、この Issue の範囲の外です。
原因
- 行き先が glob・brace の文字を含むと「決められる行き先」ではないとして、候補に何も足しません。
- 一方で「決められない」の印も立てないので、相対パスの検査は、ワークスペースの直下を基準にした判定だけで終わります。
- 行き先の語そのものは、書き込み側では glob を展開して確かめますが、展開した先がワークスペースの中なら通ります。その中の symlink は確かめません。
設計(決定:案 1)
- 1. glob・brace の文字を含む行き先は、作業ディレクトリを決められないとみなす。
cd・pushdの最初の位置引数(-P・-L・--の後も含む)が*・?・[・]・{・}のどれかを含むとき、作業ディレクトリの検査を「決められない」にします。2 段のcd、( )・{ }・ifの中、改行・;・||の後も同じです。引用やエスケープで字どおりにした括弧も同じ扱いにします(字句の読み方は、行き先が引用されていたかを区別しないため。区別は「後続の候補」)。 - 2. 「決められない」のときの検査を、
/のない語にも届かせる。 今の develop は、/のない相対の語(cat lf5のlf5)を作業ディレクトリの候補に対して確かめる分岐を、候補が 1 つ以上足されたときにしか通りません。glob・brace の行き先の印を別に持ち、その印があるときも、この分岐で拒否します。印は、glob・brace の行き先だけに立てます(CDPATHの語だけを含むenv | grep CDPATH・echo $CDPATHのような、cdのないコマンドを新しく拒否しないため)。 - 3. 書き込み側は、既存の「決められない」の拒否をそのまま使う。 相対の書き込み先は W=1 になります。保護 path の検出(P)は、glob の行き先を候補に足さないので 0 のままです。書き込みは W で止まります。
- 4. 印と理由の文。 拒否は既存の
path_reference_rejectionと、書き込み側の既存の「working directory change that cannot be determined」の理由を使います。event の名前や schema は変えません。 - 取らない案:案 2(glob を展開して候補に足す)。(a) 作業ディレクトリの検査に、ワークスペースの root を渡す変更が要ります。(b) 同じコマンドの中で先に
mkdirしたディレクトリ(mkdir s3 && cd s*)は、検査の時点の展開に現れず、実行の時点の一致と食い違います。(c) brace は複数の引数に、2 つ以上の一致はcdの失敗になり、nullglob・failglob・dotglobでも結果が変わります。単純で穴の少ない案 1 を取ります。
誤拒否の見積り
- 記録(
dev-reportsとworkspace/managementの jsonl から取った agent の Bash コマンド、重複なし 436 件。うちcd・pushdを含むもの 186 件):glob・brace を含むcdの行き先は 0 件です。試作で 436 件の値は 1 件も変わりませんでした。 - 参考(このリポジトリで動いた Claude Code の Bash コマンド、重複なし約 5.9 万件。agent の記録とは別の母集団):
cdを含む約 2.2 万件のうち、glob・brace の文字を含む行き先は 54 件でした。大半は$を含む形(cd ../repo-${n})か、ワークスペースの外の絶対パスの glob(cd /…/CommandAgent-issue-503-*)で、どちらも今も拒否されます。新しく拒否される形は、引用した字どおりの括弧の 3 件(Next.js の動的ルートのディレクトリへ引用してcdする形。例は「## 参照」)です。 - 新しく拒否される形と、書き直し方:
- glob の
cdの後の、ワークスペースの中に留まる読み書き(cd s2* && cat x、cd s2* && ls、cd s2* && cargo test)。行き先を字どおりに書けば通ります(cd s2 && …)。 - 引用した字どおりの括弧のディレクトリへ
cdする形。root から相対パスで書けば通ります(例は「## 参照」)。 cd s2*単独も拒否されます(行き先の語が「決められない」の後の相対の候補になるため)。- heredoc の本文にある
cd(cat <<EOF⏎cd s2*⏎EOF⏎cat x)も拒否されることがあります(区切りを引用しない heredoc の本文が、字句の検査に残るため)。
- glob の
受け入れ条件
- 上の表の形が、期待どおり拒否される(R=1、書き込みの形は W=1)。
- glob・brace を含まない
cd(cd sub && cat f、cd sub && cargo test、cd sub && echo x > out)、glob をcdの行き先でなく読み取りに使う形(cd src && ls *、cat s2*/x)、cdのないenv | grep CDPATH・echo $CDPATHの値が変わらない。 - 誤拒否の見積り(記録 0 件、参考の母集団で 3 件の形)と、上の「新しく拒否される形」を記録する。
- 保護 path、秘密情報、bash:verify の自動許可、[security][tools] Bash の書き込み検査の字句解析がバッククオートのコマンド置換を扱わず、中の書き込みを見逃してワークスペース外へ書き込める(#576 の調査で発見) #581・[security][tools] Bash の字句解析が二重引用の中の $(…)・${…} の " を引用の閉じと読み違え、後ろをコメントとして消してワークスペース外へ書き込める(#581 のレビューで発見) #585・[security][tools] Bash の 2 段目の path 検査が、$・glob・動的な値で静的な相対パスや展開先を確かめず、ワークスペース外を読める(#571 を統合) #582・[security][tools] Bash の書き込み先で、引用した閉じ記号と実行される glob・brace が混ざると、外向き symlink の先への書き込みを拒否しない(#582 のレビューで発見) #602・[security][tools] Bash の 2 段目の path 検査が、空白を含む静的パスと = 付きの引数を候補にせず、外を読める(#582 のレビューで発見) #603・[security][tools] Bash の閉じ込めの検査が、引用していない CR を区切りとして扱い、/ を含まない相対の語を候補にしないため、外向き symlink を通して外を読み書きできる(#602・#603 のレビューで発見) #613 の印と判定が緩む向きに変わらない。今の decision の表([security][tools] Bash の 2 段目の path 検査が、$・glob・動的な値で静的な相対パスや展開先を確かめず、ワークスペース外を読める(#571 を統合) #582 の corpus の表、H-03・H-05・H-06・H-09・H-10 の probe)で、R・A・W・P・S が 1→0 になる行と、V が 0→1 になる行が 0 件である。
- 既存の検証・受入・証跡・リリースゲートを緩めていない(AGENTS.md の honest-failure 方針)。成長上限の baseline を引き上げていない。
対象ファイル
src/tools/bash_write_guard/working_directory.rssrc/tools/bash.rstests/issue635_bash_glob_cd_destination.rs
参照
- 誤拒否の例(変更の対象ではない):
cd "src/app/api/worktrees/[id]" && …、cd …/\[id\] && …。書き直しの例:cd "src/app/[id]" && cat pageの代わりにcat "src/app/[id]/page"。 - 着手前調査の記録:ローカルの workspace/tmp/1007/wave1/results/H-11/report.md(試作の差分は SSD の wave1-20261007/h11/proto.diff)
- 発見の記録:ローカルの workspace/tmp/1007/wave1/results/H-10/report.md の「後続の候補 F1」
- 試験は新しいファイルに置きます([security][tools] Bash の閉じ込めの検査が、.. と ~ だけの語を確かめず、dd の of= を書き込みとして認識しない(#613 の調査で発見) #628 と同時に進めるとき、[security][tools] Bash の 2 段目の path 検査が、$・glob・動的な値で静的な相対パスや展開先を確かめず、ワークスペース外を読める(#571 を統合) #582 の decision の表と試験のファイルを [security][tools] Bash の閉じ込めの検査が、.. と ~ だけの語を確かめず、dd の of= を書き込みとして認識しない(#613 の調査で発見) #628 が変えるため)。
根拠(develop 2a078deb)
- glob・brace の行き先は候補に足さない:
src/tools/bash_write_guard/working_directory.rs:242-247(is_determinable)・:251-254(extend) - 「決められない」の印にもならない:
src/tools/bash_write_guard/working_directory.rs:225-229 cd・pushdの行き先の取り出し:src/tools/bash_write_guard/working_directory.rs:192-217/のない語の分岐は、候補が足されたときだけ通る:src/tools/bash.rs:829-843/のある相対の候補は「決められない」で拒否する:src/tools/bash.rs:805-811- 書き込み側の「決められない」の拒否:
src/tools/bash_write_guard.rs:198-207
後続の候補(この Issue の範囲の外)
- 引用やエスケープで字どおりにした括弧の行き先を、字句の印([security][tools] Bash の書き込み先で、引用した閉じ記号と実行される glob・brace が混ざると、外向き symlink の先への書き込みを拒否しない(#582 のレビューで発見) #602 の引用された glob の印)で区別し、字どおりの行き先として候補に足す。参考の母集団の 3 件の誤拒否がなくなります。
関連(参考)
- [security][tools] Bash の閉じ込めの検査が、引用していない CR を区切りとして扱い、/ を含まない相対の語を候補にしないため、外向き symlink を通して外を読み書きできる(#602・#603 のレビューで発見) #613
- [security][tools] Bash の書き込み検査が同じコマンドの cd・pushd の後の cwd を追わず、symlink を通ってワークスペース外へ書き込める(#509 の調査で発見) #568
- [security][tools] Bash の閉じ込めの検査が、.. と ~ だけの語を確かめず、dd の of= を書き込みとして認識しない(#613 の調査で発見) #628
- [security][tools] Bash の書き込み先で、引用した閉じ記号と実行される glob・brace が混ざると、外向き symlink の先への書き込みを拒否しない(#582 のレビューで発見) #602
- [security][tools] Bash の字句検査では、プログラム経由の間接書き込みや検証コマンドの副作用を閉じ込められない #502
Activity
Merge pull request #638 from Kewton/feature/issue-635-security-tools-…
Kewton commentedon Oct 9, 2026
PR #638 で develop に merge しました(merge commit e5c6a37、CI・acceptance・Next.js domain oracles success)。glob・brace の文字を含む cd・pushd の行き先を、作業ディレクトリを決められないとして検査するようにしました。merge 前レビュー(H-12)は blocker 0。レビューで見つかった既存の穴は #637・#502 で扱います。