Skip to content

[security][tools] Bash の書き込み検査の字句解析が ANSI-C 引用 $'…' を扱わず、program や option を見逃してワークスペース外へ書き込める(#566 の再確認で発見) #575

Description

@Kewton

背景

#566 の merge 前の再確認(2026-10-02、H-05)で見つかった見逃しです。#566 の変更とは関係がなく、develop にもとからあります(Bash の書き込み検査を入れた #206 の 5239f9b から)。2026-10-03 の着手前調査(develop@1e193ae)で、原因と直し方を確かめました。判定の関数を呼んで結果を確かめ、shell の挙動は scratchpad の中で echo・printf を使って確かめただけです。

問題(背景)

Bash の書き込み検査の字句解析(shell_tokens)は、ANSI-C 引用 $'…' と locale 引用 $"…" を知りません。$ を普通の文字として残し、続く引用を普通の引用として外すので、$'tee' は $tee という語になります。bash(macOS の /bin/sh を含む)では tee に展開されるため、program・前置き・option を見逃します。

入力(例) 今の判定 bash の実際
$'tee' /tmp/f、env $'tee' /tmp/f、env $'-S' 'tee /tmp/f' 許可(bash:verify でも自動許可) 外へ書く
$'\x74ee' /tmp/f、$'t\145e' /tmp/f、$"tee" /tmp/f、x$'tee' /tmp/f 許可 外へ書く
$'tee' sub/link/f(sub/link は外を指す symlink)、env $'-C' sub tee link/f、cp $'-t' sub/link a.txt 許可 symlink の先へ書く
echo $'\'' ; tee /tmp/f 許可 $'\'' は ' 1 文字だが、字句解析は閉じない引用と読み、後ろのコマンドを丸ごと検査しない
cat $'.env'、cat $'sub/link/secret' 許可(秘密情報の検出も素通り) 秘密や外を読む

影響(背景)

ワークスペースの外への書き込みと読み取り、秘密情報の参照の検出の素通り、bash:verify の自動許可が起きます。

決めた設計(2026-10-03)

  1. 引用の外にある $' と $" を、語の位置によらず「確かめられない書式」として拒否する(正直な失敗)。 escape を解く案は、shell の版によって escape の表が違い、$"…" は locale 次第で中身が決まらないため採らない。
  2. 判定は引用の外に限る。二重引用の中("$'x'")、単一引用の中('$')、逃がした $(\$'x')は bash でも展開されないので許可のまま。引用の状態の扱いは shell_tokens と同じにする(引用の外・'…'・"…"・\ による逃がし)。
  3. 走査は shell_tokens の前に、コマンド全体に対して行う($'\'' の読み違えで後ろが隠れる形も塞ぐため)。新しい葉の module に置き、ShellToken の種類と shell_tokens の状態の扱いは変えない([security][tools] Bash の書き込み検査が同じコマンドの cd・pushd の後の cwd を追わず、symlink を通ってワークスペース外へ書き込める(#509 の調査で発見) #568 との衝突を小さくするため)。
  4. 拒否は env -S と同じ形にする。新しい operation(例:"ANSI-C / locale quoting")の対象を write_targets に積み、confinement_rejection で拒否する。書き込みありの判定は真になり、inspect の段では拒否、bash:verify の自動許可は外れる。拒否の理由には代わりの書き方(printf 'a\tb\n'、grep -P '\t' など)を書く。event の名前と schema は変えない(operation の値が増えるだけ)。
  5. 読み取りの候補の抽出(path_tokens.rs)、秘密情報の検出(sensitive_path.rs)、verify の分類などのほかの字句解析は変えない(1 段目で先に拒否されるため)。
  6. 誤拒否として受け入れる形:printf $'a\tb\n' > out.txt、grep $'\t' a.txt、IFS=$'\n' read -r x、read -d $'\0'、echo $"hello"。記録に残る agent の Bash コマンドで $'・$" を使うものは 0 件だった。

対象ファイル

  • src/tools/bash_write_guard.rs — write_targets の最初と confinement_rejection の配線(数行)
  • src/tools/bash_write_guard/ansi_c_quoting.rs — 新規。引用の外の $'・$" の走査と単体テスト
  • tests/issue575_bash_ansi_c_quoting.rs — 新規。path_confinement_rejection を呼ぶ結合テスト

受け入れ条件

  • 下の「拒否する形」がすべて拒否され、「許可のままの形」が許可のままである
  • 書き込みありの判定で $'tee' a.txt が真になることをテストで固定した
  • 直す前の develop で新しいテストが失敗し、直した後で通ることを report に記録した(二点測定)
  • 新しい module と配線の行だけで cargo mutants を実行し(下の「作業の場所」のコマンド、上限 30 分)、生き残った変異に理由を添えて report に書いた
  • 既存の結合テスト(下の「回帰で通す既存のテスト(参考)」)は変更せず、そのまま通る
  • 既存の検証・受入・証跡・リリースゲートを緩めていない(AGENTS.md の honest-failure 方針)
  • 成長上限(docs/dev/dev-guardrails.md)の baseline を引き上げていない

テストの設計(参考)

拒否する形(/tmp/f と、fixture の sub/link → 外):

  • 問題の表の形すべて(cat の形は判定の関数で確かめるだけで、実際には読まない)
  • $'\cX' /tmp/f、$''tee /tmp/f、$""tee /tmp/f、$'sudo' tee /tmp/f、$'rm' -rf sub/link/x
  • echo $'\'' の後に改行して tee sub/link/f、printf $'a\'b' > sub/link/f
  • X=$'tee' のような代入の値

許可のままの形:

  • echo "$'x'"、echo '$'、echo \$'x'、printf '%s\n' x > out.txt、printf 'a\tb\n' > out.txt
  • 普段の検証コマンド(cargo test・cargo fmt --all -- --check・cargo clippy --all-targets -- -D warnings・npm test・npm run build・python3 -m pytest・timeout 600 cargo test など)

テストの関数名には、すべて ansi_c_quoting を含めます(変異テストの名前のフィルタで、単体テストと結合テストの両方が走るようにするため)。

回帰で通す既存のテスト(参考)

  • tests/issue566_bash_command_prefix_write_targets.rs
  • tests/issue567_bash_glob_write_targets.rs
  • tests/issue428_bash_path_tokens.rs
  • tests/bash_workspace_confinement.rs

作業の場所(参考)

  • ビルドとテストは CARGO_TARGET_DIR=/Volumes/SSD_NX/tmp/issue-575-20261003-01/target-575 と CARGO_INCREMENTAL=0 で行います。
  • 変異テストのコマンド(--lib は付けない。付けると結合テストが走らない):
    cargo mutants -f src/tools/bash_write_guard/ansi_c_quoting.rs --jobs 2 --timeout 120 -o /Volumes/SSD_NX/tmp/issue-575-20261003-01/mutants -- ansi_c_quoting
    配線の行は -F で足します。ベースラインで単体テストと結合テストの両方が走ることを確かめます。打ち切った後は、ソースを touch してビルドし直します。
  • 着手前調査の report:ローカルの workspace/tmp/1003/issue-575/results/H-01/report.md。probe:/Volumes/SSD_NX/tmp/issue-575-20261003-01/probe。

関連(参考)

根拠(develop@1e193ae)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions