Skip to content

[security][tools] Bash の字句検査では、プログラム経由の間接書き込みや検証コマンドの副作用を閉じ込められない #502

Closed

Description

@Kewton

背景

2026-09-27 の品質監査(静的解析・コードメトリクス・ブラックボックステスト)で検出した指摘です。基準コミットは develop@38efcc1e15b1 です。
対象の発見事項:FIN-002(P0)。監査の記録はローカルの workspace/tmp/0927/quality-audit-01/final/(report.md、findings.jsonl、traceability.csv)にあります。

問題

Bash の書き込み検査(bash_write_guard.rs)はコマンド文字列からリダイレクトや既知コマンドの書き込み先を抽出しますが、
python3 -c やビルド・テストスクリプト(bash:verify で自動承認される cargo test など)の中で行われる書き込みは解析できません。
最終的な実行は sh -c です。

影響

信頼できないモデル出力や、リポジトリのテスト・ビルドスクリプトが、ワークスペースの外へ書き込んだり、
凍結された検証入力を書き換えたりできる可能性があります。検証モードでは、こうした間接コマンドが自動承認されることがあります。

根拠(基準コミット時点)

監査での確認状況

  • FIN-002(P0):未確認(攻撃未実行、AG-6 NOT_RUNNABLE)

注意:この指摘はコードを読んだうえでの推論です。監査では攻撃の実行による確認ができていません(委任先の安全機能により攻撃的テストを中止したため)。対処の前に、隔離した環境で成立するかを確認してください。

対処の方向

  • 任意のシェル実行とビルド・テスト実行を「信頼できないプロセス実行」として扱い、OS レベルのファイルシステム・ネットワークの隔離(サンドボックス)を検討する。
  • 字句検査を安全境界とみなさない前提で、--yes と bash:verify の説明に限界を明記する。
  • 既存の検証・受入チェックは維持し、間接コマンドの corpus テストを追加する。

受け入れ条件

  • 隔離環境で、間接書き込み(スクリプト経由・検証コマンド経由)が成立するかを確認し、結果を記録した
  • 対処方針(サンドボックス導入または権限の縮小)を決定し、文書に限界を明記した
  • 間接コマンドの corpus テストを追加した
  • 既存の検証・受入・証跡・リリースゲートを緩めていない(AGENTS.md の honest-failure 方針)
  • 成長上限(docs/dev/dev-guardrails.md)の baseline を引き上げていない

設計の決定(2026-10-11、H-18)

設計調査(H-18、2026-10-10、develop c98bdc7c)の推奨を、利用者が 2026-10-11 にすべて承認しました。根拠は分割の Issue の本文で develop d708619a の値に書き直しています。

方式

  • Bash の閉じ込めを字句検査だけに頼らず、OS の軽量な隔離を足します。macOS は sandbox-exec(Seatbelt)、Linux は landlock です。bubblewrap・コンテナは採りません。
  • 隔離を差し込むのは、モデルの Bash(bash:verify の verify 区間を含む)と、検証・受け入れのコマンドの sh -c の起動点の 2 か所だけです。新しい葉のモジュールに置き、成長上限の対象のファイルは変えません。
  • 字句検査は残し、緩めません。拒否の理由をモデルに返す役と、隔離が使えない環境での守りとして多層の防御にします。緩めるかの判断は第 2 段の後に、別の Issue で誤拒否の実績を見て行います。

判断

# 論点 決定
1 隔離が使えない環境(入れ子の sandbox、landlock の無い kernel 等)での挙動 C:--sandbox auto|required|off。既定の auto は、使えなければ字句検査だけで続け、警告とイベントで「未適用」を記録する。required は Bash と検証を正直に失敗させる。GUI の delegate は required
2 ワークスペースの外のキャッシュへの書き込み B:既知のキャッシュ(cargo の registry・git、npm、pip、Playwright のブラウザ)だけ許可する。キャッシュへの書き込みは汚染の経路として残ることを文書に明記する
3 ネットワーク 第 1 段は変えない。第 3 段で --offline のときだけ OS で遮断する(localhost は許可)
4 読み取り隔離の範囲 A:拒否リスト(home の秘密の場所だけ拒否)。Linux ではワークスペース内の資格情報ファイル(dotenv 等)は字句検査に残る
5 隔離の範囲 第 1 段はモデルの Bash と検証コマンドだけ。第 3 段で runtime の依存の導入へ広げる

段階と分割の Issue

段 仮名 中身
1a #655:書き込み隔離(macOS) 共通の葉モジュール、--sandbox のモード、run の一時ディレクトリ、新しいイベント bash_sandbox_status、Seatbelt の書き込み隔離、間接コマンドの corpus、文書
1b #656:書き込み隔離(Linux) landlock の書き込み隔離、ubuntu の CI での確認、GUI の delegate を required にする
2 #657:読み取り隔離 home の秘密の場所の読み取り拒否(macOS はワークスペース内の資格情報ファイルも)
3 #658:ネットワークと依存の導入 --offline の OS での遮断、runtime の依存の導入への隔離の拡大

1a → 1b → 2 → 3 の順に進めます(1b・2・3 は 1a の葉モジュールに依存します)。

細部の決定(2026-10-11、分割の本文案 H-21 の確認点)

  • 1a の適用の試験は worker の環境(入れ子でない macOS)で行い、動かなければ PM が手元で記録する。
  • 3 の Linux の --offline:すべての TCP の connect を拒否し、run の dev server の port だけ許可する。同じ port の外部ホストへの接続が通ること、UDP は制限できないことを文書に書く。
  • 1b の CI の kernel と landlock の ABI は、workflow を変えずに試験の出力で記録し、CI の上で「未適用」なら試験を失敗させる。
  • 2 で home の npm の設定の読み取りを拒否する(private registry の認証を使う npm は Bash の中で失敗する。文書に書く)。
  • 判断 2 のキャッシュに pnpm・yarn の store は含めない(必要になったら足す)。

この Issue を閉じる条件

Activity

  1. Kewton commented on Sep 28, 2026

    @Kewton
    OwnerAuthor

    #501(PR #545)からの引き継ぎ:資格情報ファイルの遮断は、型付きツール(Read/Glob/Grep/Write/Edit)・MCP WorkspacePath・pack 入力・Bash の字句上の直接参照(.env や .ssh/ などを名指しするコマンド)で入りました。一方、次のような字句上秘密名が現れない Bash の読み出しは #501 の範囲外として残っています(偽のキーで確認、registry と leaf の双方で成立)。

    • 秘密でない名前のファイル symlink:head -n 1 notes.txt(notes.txt → .env)
    • ディレクトリ symlink:head -n 1 keys/id_ed25519(keys → .ssh)、head -n 1 cfg/credentials(cfg → .aws)
      あわせて、動的変数・間接 script・encoding・hardlink/複製・TOCTOU など、実行権限や隔離で解くべき経路も本 Issue の範囲です。
  2. Kewton commented on Sep 30, 2026

    @Kewton
    OwnerAuthor

    追記:#509 の着手前調査(2026-09-30、develop@beb0fdc)で確かめた間接書き込みの形

    判定の関数 commandagent::tools::bash::path_confinement_rejection を呼んで結果を確かめただけで、コマンドは実行していません。次の形は、書き込み先として認識されず許可されます。2 段目の絶対パスの検査が /usr /bin /opt /etc /tmp を読み取り用に許可しているため(src/tools/bash.rs:930-935)、書き込める先は主に /tmp です。

    形 入力(例)
    入れ子の shell sh -c 'cp a /tmp/f'、bash -c "tee /tmp/f"
    一覧にない書き込みのコマンド dd of=/tmp/f、sed -i … /tmp/f、rsync a /tmp/f
    別のコマンドを実行するコマンド find . -exec cp {} /tmp/f \;
    chmod・chown の --reference chmod --reference=a /tmp/f(skip(1) が本当の対象を読み飛ばす)
  3. Kewton commented on Oct 3, 2026

    @Kewton
    OwnerAuthor

    追記:#568 の merge 前レビュー(2026-10-03、H-02・H-03)で確かめた、文字列や名前を実行時に組み立てる形

    #568(PR #579)は、同じコマンドの中の cd・pushd・popd の後の作業場所を追い、CDPATH・DIRSTACK が出たら「行き先が決められない」として扱います。ただし、次の形は字面に cd・CDPATH・DIRSTACK が現れない、または実行時に名前が決まるため、許可されます。判定の関数を呼んで確かめ、shell の挙動は scratchpad の中で pwd を使って確かめただけです(sub/link・linked-outside は外を指す symlink)。

    形 入力(例) 結果
    名前を組み立てる代入 n=CD; export "${n}PATH=sub"; cd link; tee f、同じ形で cat secret 外へ書く・外を読む(読み取りは bash:verify で自動許可)
    同上(DIRSTACK) n=DIR; declare "${n}STACK[1]=sub/link"; popd; tee f、同じ形で cat secret 同上
    文字列として実行 eval cd sub && tee link/f、trap 'cd sub' DEBUG; tee link/f 外へ書く
    そのほか source・.、alias g='cd sub' の後の g、X=cd; $X sub && tee link/f、cd sub && sh -c 'tee link/f' 外へ書く
  4. Kewton commented on Oct 4, 2026

    @Kewton
    OwnerAuthor

    #576 の確認で見つかった、この Issue の範囲の形(2026-10-04)

    #576(コメントと heredoc の本文の中の ' で後ろのコマンドが隠れる問題)の直した箇所の確認で、次の形が見つかりました。shell の字句解析では決めきれないため、#576 の範囲から外して、この Issue で扱います。判定の関数に入力して確かめただけで、実際には書き込み・読み取りをしていません。

    形(判定の関数への入力) 今の判定 理由
    python3 <<'EOF'⏎pass#'⏎open("sub/""link/f","w")#'⏎EOF 許可 Python では隣り合う文字列がつながり sub/link/f になる。shell の読み方では path が 2 つに分かれる
    python3 <<'EOF'⏎pass#'⏎open("sub/""link/secret").read()#'⏎EOF 許可 同じ。外の読み取り
    python3 <<'EOF'⏎pass#'⏎open(".e""nv").read()#'⏎EOF 許可・秘密情報の検出なし 同じ。秘密情報の名前が分かれる

    根拠:workspace/tmp/1003/issue-576/results/H-05/report.md(B2)。

  5. Kewton commented on Oct 7, 2026

    @Kewton
    OwnerAuthor

    2026-10-07:#582 の merge 前レビュー(H-03・H-04)で、字句検査では閉じ込められない形を追加で確かめました。どれも #582 の前の develop にもあり、#582 では直していません。判定の関数を tempdir の fixture に対して呼び、結果を見ただけです(外の実際の読み取りは試していません)。

    入力(例) 閉じ込めの判定 R 内容
    X=sub/link/secret; echo $X | xargs cat 0 変数の値を別のコマンドに渡して読む
    echo $HOME/x | xargs cat 0 同上
    echo "cat $X" | sh、sh -c '…'、eval '…' 0 文字列を shell に実行させる
    X=sub/link/*; echo $X 0 外のディレクトリの名前の一覧が出る(中身は出ない)
    echo $HOME/*、echo $?/* 0 echo の動的な語の後の glob で、名前の一覧が出る(#582 で例外とした echo の表示の範囲)
  6. Kewton commented on Oct 7, 2026

    @Kewton
    OwnerAuthor

    2026-10-08:#602・#603 の merge 前レビュー(H-06)で、字句検査で閉じ込められない形を追加で確かめました。どれも #602・#603 の前の develop にもあります。判定の関数を tempdir の fixture に対して呼び、結果を見ただけです。

    入力(例) 閉じ込めの判定 R 内容
    BASH_ENV=sub/link/secret bash -c true、LD_PRELOAD=…、GIT_CONFIG_GLOBAL=… 0 ファイルを読む環境変数。先頭の代入は #603 の設計で対象外にしている
    grep -fsub/link/secret x、--opt=a=sub/link/secret、--opt==… 0 オプションに値を続けた形、= を 2 つ含む形
    sh -c 'cat sub/link/secret' 0(V=1) sh -c・bash -c の本文は、閉じ込めの判定で調べていない
    • 引用していない CR と、/ を含まない相対の語は、別に起票しました(security)。
    • 環境変数や実行される文字列の中身は、字句検査では追い切れません。この Issue の「OS レベルの隔離」か「権限の縮小」の判断に含めてください。
  7. Kewton commented on Oct 9, 2026

    @Kewton
    OwnerAuthor

    2026-10-09:#635 の merge 前レビュー(H-12)で、字句検査で閉じ込められない形を追加で確かめました。どれも #635 の前の develop にもあります。判定の関数を tempdir の fixture に対して呼び、結果を見ただけです。

    入力(例) 閉じ込めの判定 R 内容
    eval 'cd s2* && cat lf4'、eval cd s2* \&\& cat lf4 0 eval の中身は読まれない
    cd s@(2) && cat lf4、cd s+(2) && cat lf4 0 extglob の形は glob の印が立たない(起動は sh -c で extglob は既定で無効なので、優先度は低い)
    • eval や実行される文字列の中身は、字句検査では追い切れません。この Issue の「OS レベルの隔離」か「権限の縮小」の判断に含めてください。
  8. added a commit that references this issue on Oct 12, 2026
  9. Kewton commented on Oct 13, 2026

    @Kewton
    OwnerAuthor

    設計の決定(2026-10-11)どおり、書き込み隔離の 1a・1b の merge で閉じます。

    受け入れ条件のうち「間接書き込みの確認」「方針の決定と限界の明記」「間接コマンドの corpus テスト」は #655・#656 で満たし、検証・受入・証跡のゲートと成長上限の baseline は変えていません。

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