Skip to content

[security][tools] Bash と検証コマンドの書き込みを OS の隔離で閉じ込める(第 1 段 a:macOS の Seatbelt と共通の土台) #655

Closed

Description

@Kewton

背景

#502 の第 1 段 a です。#502 の設計調査(H-18、2026-10-10)の推奨を、利用者が 2026-10-11 にすべて承認しました(#502 の「設計の決定」の節)。

Bash の閉じ込めは字句検査だけで、sh -c の本文・eval・xargs・find -exec・インタープリタのコード・検証コマンドが呼ぶビルドやテストのスクリプトの中の書き込みは、原理的に追えません。H-18 の試作(macOS 27.0.1、無害な fixture)では、Seatbelt で書き込みをワークスペースに限ると、これらの形の外への書き込みがすべて Operation not permitted になり、外の inode への hardlink の作成も拒否されました。

問題

  • モデルの Bash と、bash:verify で自動承認される verify 区間のコマンドは、字句検査を通った後、そのまま sh -c で起動されます。字句に現れない書き込み(プログラムの中の書き込み)は止まりません。
  • 検証・受け入れのコマンドは、字句の拒否(blocked_reason)しか通らず、閉じ込めの判定を通りません。ビルドやテストのスクリプトがワークスペースの外や凍結された検証入力に書けます。
  • GUI の delegate は常に read,write,bash:verify で起動するので、この経路の影響が最も大きくなります。

設計(決定)

  1. 葉モジュール:新しい葉のモジュールに、Command を隔離して起動する wrap を置きます。方針(書き込みを許す場所の一覧)とモードを受け取り、適用した・未適用(理由つき)を返します。macOS 以外では「未適用(backend なし)」を返す stub にします(Linux は 1b)。
  2. モード(判断 1):CLI に --sandbox auto|required|off を足します。既定は auto。
    • auto:使えなければ字句検査だけで続け、起動時に警告を 1 回出し、イベントで「未適用」と理由を記録します。
    • required:使えなければ、Bash と検証を新しい理由 bash_sandbox_unavailable で正直に失敗させます(Blocked)。
    • off:今と同じ挙動です。
    • 利用可否は run の開始時に 1 回だけ確かめます(macOS は何もしない profile で true を起動。入れ子の sandbox の中では rc=71)。
    • モードと利用可否は、プロセス全体の状態として run の開始時に設定します。今の許可の方針は thread_local なので、別スレッドから起動する Bash にも届く形(OnceLock 等)を選びます。
  3. macOS の backend:sandbox-exec -p <固定の profile> -D WS=<正規化した root> -D TMP=<run の一時ディレクトリ> … sh -c <command>。profile は文字列定数で、場所は -D の param だけで渡します(profile に場所を埋め込まない)。書き込みを許すのは、ワークスペース・run の一時ディレクトリ・null や tty 等のデバイス・判断 2 のキャッシュだけです。読み取りとネットワークは変えません。
  4. キャッシュ(判断 2):cargo の registry と git のキャッシュ(cargo の package cache の lock を含む)、npm のキャッシュ、pip のキャッシュ、Playwright のブラウザのキャッシュだけ許可します。pnpm・yarn の store は含めません(決定 2026-10-11。必要になったら別の Issue で足す)。具体的な場所は「参照」。rustup の toolchain の導入は許可しません(rust-toolchain による自動導入は失敗します)。キャッシュへの書き込みは「汚染」の経路として残ることを文書に書きます。
  5. run の一時ディレクトリ:state-dir の下に run ごとの一時ディレクトリを作り、そこだけを書き込み許可に入れ(親の state-dir は許可しない)、TMPDIR を明示で子に渡します。子の環境変数の許可リストは、明示のときだけ TMPDIR を通すようにします。
  6. 起動点(判断 5):モデルの Bash の sh -c(verify 区間も同じ関数を通る)と、検証・受け入れのコマンドの sh -c の 2 か所だけで wrap を呼びます。どちらも数行の配線です。runtime 自身の起動(git の状態・ブラウザの probe・依存の導入)は範囲の外です。
  7. イベント:新しいイベント bash_sandbox_status(mode・backend・applied・reason)を run の開始時に 1 回出し、秘密の消去で消えない固定の識別子の一覧に足します。既存のイベントの名前と schema は変えません。
  8. 文書:--yes・--allow bash:verify・--offline・--sandbox の説明とセキュリティの文書の限界を、「OS が閉じるもの(隔離が使えるとき、Bash と検証コマンドの書き込み)」と「字句検査だけのもの(読み取り、ネットワーク、隔離が使えないとき)」に分けて書き換えます。bash:verify の自動承認は「検証の分類を通るコマンド」であり、そのコマンドが呼ぶスクリプトの中身は隔離(ある場合)だけが制限する、と明記します。
  9. 字句検査は変えません。隔離が適用されていても、字句の拒否はそのまま先に働きます。

規模の見積り

  • 新規:葉モジュール 約 170 行、macOS の backend 約 110 行(profile の定数を含む)。合わせて約 280〜320 行。
  • 配線:CLI 約 12 行、run の開始時の設定・警告・イベント 約 15 行、2 つの起動点 各 3〜4 行、環境変数の許可リスト 1〜2 行、固定の識別子 1〜2 行、モジュールの宣言 1 行。合わせて約 40 行。
  • 試験:約 250〜350 行と corpus。新規 500 行を超えない見込みなので、分けません。

受け入れ条件

  • macOS(入れ子でない環境)で、ワークスペースの中の symlink(tempdir の中の「外」を指す)に向けた sh -c 'cp …'・eval・xargs cp・python3 -c の open(…,'w')・find -exec cp・dd of=・sed -i、システムの一時ディレクトリへの直接の書き込みが隔離で失敗し、「外」の fixture が変わらないことを試験で確かめる。試験の「外」は tempdir の中だけで、実際のワークスペースの外へは書かない。
  • 外の inode への hardlink の作成が隔離で失敗する。
  • 検証・受け入れのコマンドの経路でも、同じ形の外への書き込みが隔離で失敗する。
  • 隔離の中で、mktemp・git status・cargo --version・node -e・python3 -c が成功する。依存のない crate の cargo build、キャッシュ済みの fixture の npm ci、Next.js の next build(fixture)、Playwright で Chromium を 1 回起動することを隔離の中で試し、結果(成功・失敗と原因)を記録する。失敗するもの(Chromium の入れ子の sandbox 等)は、対処(許可の追加・--no-sandbox の要否)または「隔離の中では動かない」ことを文書に書く。
  • 隔離が使えないとき:auto は字句検査だけで続け、起動時の警告と bash_sandbox_status(applied=false、理由つき)を出す。required は Bash と検証を bash_sandbox_unavailable で失敗させる。どちらも「隔離した」と表示しない。入れ子の sandbox(rc=71)を模した試験で確かめる。
  • --sandbox off と、macOS 以外(隔離が未適用)の経路で、字句検査の判定が今と同じである(今の decision の表で、R・A・W・P・S が 1→0 になる行と、V が 0→1 になる行が 0 件)。
  • 隔離が適用された状態の試験の結果を、入れ子でない macOS で記録する(入れ子の環境では適用の試験が走らないため。記録が無ければ受け入れない)。Linux の CI では、macOS の backend の試験は外れ、off と「未適用」の経路の試験が走る。
  • 間接コマンドの corpus を追加した(期待の表を含む)。
  • 字句検査を緩めていない。
  • event の既存の名前と schema を変えていない(bash_sandbox_status の追加だけ)。
  • 既存の検証・受入・証跡・リリースゲートを緩めていない(AGENTS.md の honest-failure 方針)。
  • 成長上限の baseline を引き上げていない。成長上限の対象のファイル(「参照」の一覧)を変えていない。
  • cargo fmt --all -- --check・cargo clippy --all-targets -- -D warnings・cargo test が通る。

対象ファイル

  • src/tools/os_sandbox.rs(新規)
  • src/tools/os_sandbox/seatbelt.rs(新規)
  • src/tools/mod.rs
  • src/tools/bash.rs
  • src/minimal_loop/verifier_env.rs
  • src/bounded_process.rs
  • src/cli.rs
  • src/lib.rs
  • src/sensitive_data/fixed_identifiers.rs
  • tests/issue502_os_sandbox_write.rs(新規)
  • tests/corpus/apps/issue502-indirect-write/expectations.toml(新規)
  • tests/corpus/apps/issue502-indirect-write/(新規。fixture)
  • SECURITY.md
  • docs/guide/ja/cli-reference.md
  • docs/guide/en/cli-reference.md

参照

  • 成長上限の対象のファイル:src/planner/runner.rs・src/minimal_loop/loop_run.rs・src/minimal_loop/loop_run/runtime_bash_effects.rs(変えない)
  • 適用の試験の環境(決定 2026-10-11):worker の環境で行う。PM のセッションでは sandbox-exec が入れ子でなく動く(rc=0)ことを確かめた。dispatch の前にリーダーが worker の環境で同じことを確かめ、動かなければ PM が手元で記録する。
  • 設計調査の記録:ローカルの workspace/tmp/1007/wave1/results/H-18/report.md(試作のログは SSD の wave1-20261007/h18/probe-log.md)
  • 判断 2 のキャッシュの場所:~/.cargo/registry・~/.cargo/git・~/.cargo/.package-cache、~/.npm、~/Library/Caches/pip、~/Library/Caches/ms-playwright(Linux の 1b では ~/.cache/pip・~/.cache/ms-playwright)
  • macOS の mktemp は TMPDIR を見ない形がある(H-18 の試作)。TMPDIR を渡したうえで mktemp の成功を確かめる。
  • corpus は tests/corpus_regression.rs が tests/corpus/apps/ の下を走査する。
  • 実装は試験の「外」を tempdir の中に置く。実際の home・ワークスペースの外には書かない。

根拠(develop d708619a)

  • モデルの Bash の起動点:src/tools/bash.rs:229-238(Command::new("sh")、run_with_timeout_cancel_and_force)。前段の字句検査:src/tools/bash.rs:177-227(placeholder・sensitive_bash_rejection・structural_blocked_reason・path_confinement_rejection)
  • モデルの Bash の呼び出し:src/tools/registry.rs:175-186。verify 区間:src/minimal_loop/loop_run/runtime_bash_effects.rs:65-74(同じ run_structured_cancel_and_force)
  • 検証・受け入れのコマンドの起動点:src/minimal_loop/verifier_env.rs:225-246(字句は blocked_reason だけ、sh -c は :239-240)。呼び出し:src/minimal_loop/completion.rs:351、src/minimal_loop/build_verifier.rs:549、src/minimal_loop/build_verifier/typescript.rs:31、src/planner/verify.rs:1038,1118
  • 子の環境変数の許可リスト(TMPDIR が無い):src/bounded_process.rs:51-110(child_env_allowed は :87-108)
  • プロセスグループ:src/bounded_process.rs:518-524
  • 自動承認:src/tools/allow_policy.rs:122-143。許可の方針は thread_local:src/tools/allow_policy.rs:38-68、設定は src/lib.rs:76
  • CLI の --yes・--allow・--offline:src/cli.rs:111-127、src/cli.rs:505-512
  • GUI の delegate は常に read,write,bash:verify:src/bin/gui_server/delegate.rs:37,299-300
  • イベントの出力:src/eval_events.rs:144-160。固定の識別子:src/sensitive_data/fixed_identifiers.rs:45-56
  • 文書の今の約束:SECURITY.md:14-19,21-34,41-43,111-120、docs/guide/ja/cli-reference.md:32-33,80、docs/guide/en/cli-reference.md:33-34,81
  • 成長上限:docs/dev/dev-guardrails.md:9-27

関連(参考)

Activity

  1. added a commit that references this issue on Oct 12, 2026
  2. Kewton commented on Oct 12, 2026

    @Kewton
    OwnerAuthor

    PR #661 で merge しました(develop 936dbd3、CI・acceptance success)。後続の論点は #659・#656・#658 に記録しています。

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions