Skip to content

#655 [security][tools] Bash と検証のコマンドの書き込みを macOS の Seatbelt で隔離する(#502 の 1a) - #661

Merged
Kewton merged 3 commits into
developfrom
feature/issue-655-security-tools-bash-os-1-a-macos-seatbelt
Oct 12, 2026
Merged

Kewton merged 3 commits into
developfrom
feature/issue-655-security-tools-bash-os-1-a-macos-seatbelt

Conversation

@Kewton

@Kewton Kewton commented Oct 12, 2026

Copy link
Copy Markdown
Owner

概要

Bash ツールと検証コマンドの子プロセスを、macOS の Seatbelt(sandbox-exec)で包みます。ワークスペース・run の一時ディレクトリ・必要なキャッシュの外には書けないようにします。#502 の段階 1a です。字句の検査(bash_write_guard など)は変えず、その後ろに OS の層を足します。

Refs #655
Refs #502

変更(16 ファイル、+1351 −9、2 commit)

  • 新しい葉のモジュール
    • src/tools/os_sandbox.rs:SandboxMode(auto・required・off)、利用できるかの判定、SandboxPolicy、プロセス全体の状態、install・status・startup_warning・wrap_command。
    • src/tools/os_sandbox/seatbelt.rs:profile は固定の定数です。場所は -D の param だけで渡します(WS・TMP・キャッシュ 6 種)。probe・classify_probe_exit・wrap もここにあります。
  • 配線
    • bash.rs・verifier_env.rs:sh -c の直後に wrap_command を呼びます。required で隔離が使えないときは、spawn の前に Blocked と bash_sandbox_unavailable を返します。
    • lib.rs:run の開始時に install を呼び、auto で使えないときは警告を出し、bash_sandbox_status の event を出します。
    • cli.rs:--sandbox。今は隠しフラグです(下の「後続の論点」を参照)。
    • bounded_process.rs:TMPDIR を通すのは、明示したときだけです。
    • fixed_identifiers.rs・mod.rs:識別子とモジュールの宣言。
  • 試験:tests/issue502_os_sandbox_write.rs(10 本、うち applied_cargo_build_succeeds は ignored)と corpus tests/corpus/apps/issue502-indirect-write/。
  • 文書:SECURITY.md、cli-reference の en・ja。
  • 成長上限のファイル(runner.rs・loop_run.rs・runtime_bash_effects.rs)と baseline は変えていません。event は bash_sandbox_status を足しただけです。
  • 子の環境変数は、spawn の時点で env_clear の後に許可リストが当たります(apply_child_env_allowlist)。親の秘密が子に届く経路は増えていません。

検証

  • 15 ゲート:0a140c1c と、修正後の HEAD 36d0b3eb の 2 回とも 15/15 passed でした。
  • 隔離を適用した状態の試験:入れ子でない macOS で --include-ignored を付けて流し、10 本すべて pass、SKIP 0 でした。
    • 間接の書き込み(sh -c cp・eval・xargs cp・python3 open(w)・find -exec cp・dd of=・sed -i)が拒否され、外の fixture は変わりません。
    • 外の inode への hardlink、システムの一時ディレクトリへの書き込み、検証コマンドの経路での書き込みも拒否されます。
    • 必要な道具(mktemp -p "$TMPDIR"・git・cargo・node・python3)と、依存のない cargo build は隔離の中で成功します。
    • probe の結果と、直接実行した sandbox-exec の結果が一致します。
  • heavy probe:seatbelt.rs の PROFILE をそのまま使いました。
    • 外への書き込みは拒否されました。
    • cargo build --offline、npm ci --offline(GUI の写し)、next build、Playwright(キャッシュ済みの headless Chromium で起動・描画・終了)は、どれも成功しました。
  • 二点測定
    • develop 9047eb35 に新しいテストを置くと、os_sandbox が無いので compile の時点で FAIL します。
    • HEAD で apply_backend を素通しにすると(develop と同じく隔離なし)、隔離を適用した状態の試験が 5 本 FAIL します。
  • cargo mutants(--jobs 1 --timeout 300、対象は os_sandbox.rs・seatbelt.rs)
    • 1 回目は caught 22・missed 8・unviable 11 でした。heavy-lock のメモリ停止はありません。停止があったとしても caught には数えていません。
    • missed のうち、許可が増える向きは 2 件でした。どちらも probe の判定 output.success() の変異です。36d0b3eb で分類を classify_probe_exit に分け、テストを足して直しました。
      • cargo-mutants はこの if を変異させないので、if true と if false を手で当てました。どちらもテストで捕まりました(単体テストと probe_matches_a_direct_sandbox_exec_run)。
    • 残る missed 6 件は、event の label(backend_name)か、キャッシュの許可が減る厳しい側の変異です。
  • merge 前レビュー(H-23):merge 可、blocker 0。
    • profile は書き込みの 21 種を外側で拒否しました。param への注入や symlink を使った回り込みも効きません。
    • 字句の判定は、2507 行で差が 0 件でした。

後続の論点(PM が別の Issue と #656・#658 に記録します)

🤖 Generated with Claude Code

Kewton and others added 3 commits October 11, 2026 19:33
…t, stage 1a)

Wrap the two `sh -c` launch points — the model Bash tool (the `bash:verify`
interval reuses the same function) and verifier/acceptance commands — in a new
`os_sandbox` leaf module. When the Seatbelt backend is available, writes are
confined to the workspace, the run's temp directory, the known dependency
caches, and device files, so an indirect write the lexical guard cannot see
fails. The lexical guard still runs first and is not weakened.

`auto` continues with lexical checks only when the backend is unavailable and
records `bash_sandbox_status` (applied=false, reason); `required` fails with
`bash_sandbox_unavailable`; `off` keeps the previous behavior. The event name is
the only new one; existing event names and schemas are unchanged. The run temp
directory is passed to children via an explicit `TMPDIR`.

読み替え: `--sandbox` is hidden from the public flag table because the advertised
flag count lives in docs/guide/README.md, which this issue's scope.allow does not
include; it is documented in prose in SECURITY.md and both cli-reference pages
instead.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Split the probe's exit-status decision into `classify_probe_exit` so the
"only rc=0 is Available" rule is fixed by a unit test (rc=0, rc=71 with the
status in the reason, and a missing status), and add a macOS integration test
that asserts `probe()` matches a direct `sandbox-exec` run, which stays honest
under a nested sandbox. Product behavior, the lexical guard, events, and
existing expectations are unchanged.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
On non-macOS the applied tests are excluded, so their helpers, fixture
field, and imports became dead code and broke `-D warnings` on Linux CI.
Gate `outside_secret`, `read_secret`, `backend_available`, `install_available`,
`run_wrapped`, `run_wrapped_success`, the `IndirectCase` fields only the applied
tests read, and the `std::process`/`bounded_process` imports. Test names,
counts, expectations, `src/`, and the corpus are unchanged.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@Kewton
Kewton merged commit 936dbd3 into develop Oct 12, 2026
5 checks passed
@Kewton
Kewton deleted the feature/issue-655-security-tools-bash-os-1-a-macos-seatbelt branch October 12, 2026 01:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant