Repository navigation
[security][tools] Bash と検証コマンドの書き込みを OS の隔離で閉じ込める(第 1 段 a:macOS の Seatbelt と共通の土台) #655
Description
背景
#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で起動するので、この経路の影響が最も大きくなります。
設計(決定)
- 葉モジュール:新しい葉のモジュールに、
Commandを隔離して起動するwrapを置きます。方針(書き込みを許す場所の一覧)とモードを受け取り、適用した・未適用(理由つき)を返します。macOS 以外では「未適用(backend なし)」を返す stub にします(Linux は 1b)。 - モード(判断 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等)を選びます。
- macOS の backend:
sandbox-exec -p <固定の profile> -D WS=<正規化した root> -D TMP=<run の一時ディレクトリ> … sh -c <command>。profile は文字列定数で、場所は-Dの param だけで渡します(profile に場所を埋め込まない)。書き込みを許すのは、ワークスペース・run の一時ディレクトリ・null や tty 等のデバイス・判断 2 のキャッシュだけです。読み取りとネットワークは変えません。 - キャッシュ(判断 2):cargo の registry と git のキャッシュ(cargo の package cache の lock を含む)、npm のキャッシュ、pip のキャッシュ、Playwright のブラウザのキャッシュだけ許可します。pnpm・yarn の store は含めません(決定 2026-10-11。必要になったら別の Issue で足す)。具体的な場所は「参照」。rustup の toolchain の導入は許可しません(
rust-toolchainによる自動導入は失敗します)。キャッシュへの書き込みは「汚染」の経路として残ることを文書に書きます。 - run の一時ディレクトリ:state-dir の下に run ごとの一時ディレクトリを作り、そこだけを書き込み許可に入れ(親の state-dir は許可しない)、
TMPDIRを明示で子に渡します。子の環境変数の許可リストは、明示のときだけTMPDIRを通すようにします。 - 起動点(判断 5):モデルの Bash の
sh -c(verify 区間も同じ関数を通る)と、検証・受け入れのコマンドのsh -cの 2 か所だけでwrapを呼びます。どちらも数行の配線です。runtime 自身の起動(git の状態・ブラウザの probe・依存の導入)は範囲の外です。 - イベント:新しいイベント
bash_sandbox_status(mode・backend・applied・reason)を run の開始時に 1 回出し、秘密の消去で消えない固定の識別子の一覧に足します。既存のイベントの名前と schema は変えません。 - 文書:
--yes・--allow bash:verify・--offline・--sandboxの説明とセキュリティの文書の限界を、「OS が閉じるもの(隔離が使えるとき、Bash と検証コマンドの書き込み)」と「字句検査だけのもの(読み取り、ネットワーク、隔離が使えないとき)」に分けて書き換えます。bash:verify の自動承認は「検証の分類を通るコマンド」であり、そのコマンドが呼ぶスクリプトの中身は隔離(ある場合)だけが制限する、と明記します。 - 字句検査は変えません。隔離が適用されていても、字句の拒否はそのまま先に働きます。
規模の見積り
- 新規:葉モジュール 約 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.rssrc/tools/bash.rssrc/minimal_loop/verifier_env.rssrc/bounded_process.rssrc/cli.rssrc/lib.rssrc/sensitive_data/fixed_identifiers.rstests/issue502_os_sandbox_write.rs(新規)tests/corpus/apps/issue502-indirect-write/expectations.toml(新規)tests/corpus/apps/issue502-indirect-write/(新規。fixture)SECURITY.mddocs/guide/ja/cli-reference.mddocs/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
関連(参考)
-
[security][tools] Bash の字句検査では、プログラム経由の間接書き込みや検証コマンドの副作用を閉じ込められない #502(親。1a と 1b の merge で閉じる)
-
1b:書き込み隔離(Linux の landlock)、2:読み取り隔離、3:ネットワークと依存の導入
-
[security][tools] 通常の Read で、モデルがワークスペースの .env(API キー)を承認なしに読める #501、[security][tools] Bash の 2 段目の path 検査が、$・glob・動的な値で静的な相対パスや展開先を確かめず、ワークスペース外を読める(#571 を統合) #582、[security][tools] Bash の cd・pushd の行き先が 1 つに一致する glob だと作業ディレクトリが検査されず、外を読み書きできる(#613 のレビューで発見) #635
-
分割の Issue:1a [security][tools] Bash と検証コマンドの書き込みを OS の隔離で閉じ込める(第 1 段 a:macOS の Seatbelt と共通の土台) #655(macOS の書き込み隔離と共通の土台)、1b [security][tools] Bash と検証コマンドの書き込みを Linux の landlock で閉じ込める(第 1 段 b) #656(Linux の書き込み隔離)、2 [security][tools] Bash と検証コマンドの、home の秘密の場所の読み取りを OS の隔離で拒否する(第 2 段:読み取り隔離) #657(読み取り隔離)、3 [security][tools]
--offlineの外向きの通信を OS の隔離で遮断し、runtime の依存の導入を隔離の中で動かす(第 3 段) #658(ネットワークと依存の導入)
Activity
Merge pull request #661 from Kewton/feature/issue-655-security-tools-…
Kewton commentedon Oct 12, 2026
PR #661 で merge しました(develop 936dbd3、CI・acceptance success)。後続の論点は #659・#656・#658 に記録しています。