feat(claude): ループバックを sandbox 内で許可し承認プロンプトを削減する - #81
Merged
Conversation
直近8日の全セッション(Bash 呼び出し 10,635 件)を集計したところ、承認 プロンプトは週約 2,400 回発生していた。発生源は3つで、いずれも許可リスト の不足ではない。`autoAllowBashIfSandboxed` があるためサンドボックス内で 完結するコマンドは既に自動許可されており、`npm run test` などを allow に 足しても何も変わらない。 最大の発生源はサンドボックス外実行(週 1,941 回)で、その 47%(926 回)が ローカル TCP の遮断によるものだった。サンドボックス内で実測すると `connect 127.0.0.1:5432` と `bind/listen 127.0.0.1:0` が両方とも EPERM で 落ちる。統合テスト、E2E、dev サーバ、Playwright、Go の httptest、 `curl http://localhost:*` がこれで失敗し、そのたびに昇格が必要になり、 bash-guard.sh が承認を求めていた。 `sandbox.network.allowLocalBinding` を有効にすると seatbelt プロファイルに network-bind / network-inbound / localhost への network-outbound が出る。 適用後に同じ測定を行い、両方とも成功することを確認した。外部ネットワーク の制限は変わらない。 第二の発生源は docker で、`excludedCommands` により常にサンドボックス外で 走るのに allow ルールが無く、週約 440 回プロンプトを出していた。読み取り 専用のサブコマンドだけを allow に加える。`docker exec` と `compose exec` はコンテナ内で任意コマンドを実行できるため承認を維持する。 第三に、週3回以上使っている MCP の読み取り系ツール3件を allow に加える。 あわせて、承認ダイアログに出るのが description だけであるのに、ヒア ドキュメントで渡す長いスクリプトの説明が無く可否を判断できない問題を直す。 bash-guard.sh に、承認を要する経路(escape と docker の変更系)で description が 80 バイト未満なら deny を返す門を追加した。ask ではなく deny なのは、deny の理由だけがモデルに返るため、説明を書き直させられる からである。ask にするとユーザに丸投げになる。サンドボックス内で完結する コマンドは対象外なので、通常の作業に摩擦は増えない。 見込みは週約 2,400 回から約 1,100 回。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
escape を ask とだけ期待していた検査が、説明必須ゲートの追加で落ちていた。 仕様が変わったのは意図どおりなので、検査側を追随させる。 `assert_decision` に description を渡せるようにし、既存の「サンドボックス外」 の検査には十分な長さの説明を与えて ask のままにした。そのうえで、新しい門の 振る舞いを4つの観点で固定する。 - 承認を要する経路(escape、docker の変更系)で説明が無い・短いと deny になる - 読み取り専用の docker とサンドボックス内のコマンドは説明を求めない。ここを 縛ると通常の作業に摩擦だけが増えるため、素通りであることを明示的に固定する - 閾値 80 バイトの境界。79 で deny、80 で ask - 破壊的操作の deny は説明の有無より優先する。説明を書けば force push が 通ってしまう、という抜け道が無いことを示す あわせて `docker-compose.yml` を含む grep が docker の変更系として誤検知 されないことも固定した。 pass=36 から pass=55 になる。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`/auto-mode-setup` が生成した `autoMode` ブロックを管理下に入れる。取り込ま
ないままだと `chezmoi apply` が実ファイルから 180 行を消すドリフトが残る。
同時に `settings.json` を `settings.json.tmpl` に変える。`autoMode` の説明文
にホームディレクトリの絶対パスが3箇所あり、そこにアカウント名が入る。この
リポジトリは public で、これまで同じ文字列を一度も含んでいない。`{{ .chezmoi.homeDir }}`
に置き換えて公開物に残らないようにした。
`chezmoi execute-template` の出力が現行の `~/.claude/settings.json` と完全に
一致することを確認済み。3ファイルとも `chezmoi diff` は空である。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
直近8日の全セッション(377 ファイル / Bash 呼び出し 10,635 件)を集計した。承認プロンプトは週約 2,400 回、1日あたり約 350 回発生していた。
発生源は3つで、いずれも許可リストの不足ではない。
sandbox.autoAllowBashIfSandboxed: trueがあるため、サンドボックス内で完結するコマンドは既に無条件で自動許可されている。Bash(npm run *)やBash(npx *)を allow に足しても何も変わらない。dangerouslyDisableSandbox: truebash-guard.shが無条件にaskを返す(設計どおり)excludedCommandsのdocker *① ローカル TCP の遮断が最大の原因(週 926 回、①の 47%)
サンドボックス外実行 1,941 件を分類した内訳。
サンドボックス内で実測して原因を確定した。
npm run test:integration(Postgres 5432)、npm run test:e2eと Playwright のwebServer、npm run db:seed、curl http://localhost:3000(週 107 回)、Go のhttptestがこれで落ち、そのたびに昇格が必要になっていた。sandbox.network.allowLocalBinding: trueを入れると、seatbelt プロファイルに次が出る。外部ネットワークの制限は一切変わらない。緩むのはループバックだけである。
② docker は読み取り専用のみ allow(週約 40 回)
docker *はexcludedCommandsにあるため常にサンドボックス外で走るが、allow ルールが無いので毎回プロンプトが出ていた。git *とgh *には既にルールがある。読み取り専用のサブコマンドだけを追加した。
docker exec(週 243 回)とdocker compose exec(週 128 回)はコンテナ内で任意コマンドを実行できるため、意図して承認を維持している。③ MCP 読み取り系(週約 58 回)
週3回以上使っているものだけを追加した。
notion-search(1) などは見送っている。説明不足のまま承認を求める問題
承認ダイアログに出るのは
descriptionだけだが、ヒアドキュメントで渡す長いスクリプトの説明が無く、ユーザが可否を判断できない状態だった。bash-guard.shに、承認を要する経路に限ってdescriptionが 80 バイト未満ならdenyを返す門を追加した。askではなくdenyにしたのは、denyの理由だけがモデルに返るためである。説明を書き直して再提示させられる。askだと承認ダイアログをユーザに丸投げすることになる。サンドボックス内で完結するコマンドはプロンプトが出ないので対象外にしてある。通常の作業に摩擦は増えない。
テスト
bash -nに加えて、フックへペイロードを直接渡して 11 パターンの判定を確認した(適用前の候補ファイルと、インストール後の実ファイルの両方で実施)。npm run test:integration(サンドボックス内)docker exec … psql・説明 8 バイトdocker exec … psql・説明が十分docker ps/docker compose psdocker compose up -d・説明が短いgit addに escape・説明が短い適用後に実環境で再測定した。
セッションの再起動は不要で、適用直後から反映された。
見込み
週約 2,400 回 → 約 1,100 回。内訳は
allowLocalBindingで 926 回、docker 読み取りで約 40 回、MCP で約 58 回。残るのは escape 付き実行の裾(説明が付くようになる)と
docker exec系である。追加コミット
test(claude): 検査を新しい仕様に追随させるescapeをaskとだけ期待していた既存の検査が、説明必須ゲートの追加で落ちていた。仕様が変わったのは意図どおりなので、検査側を追随させた。assert_decisionにdescriptionを渡せるようにし、新しい門の振る舞いを4つの観点で固定している。denydeny、80 でaskdenyは説明の有無より優先する。説明を書けば force push が通る、という抜け道が無いことを示すあわせて
docker-compose.ymlを含む grep が docker の変更系として誤検知されないことも固定した。pass=36 → pass=55、fail=0。feat(claude): auto-mode の設定を取り込み、テンプレート化する当初このPRから外していた
/auto-mode-setup生成のautoModeブロックを管理下に入れた。取り込まないままだとchezmoi applyが実ファイルから 180 行を消すドリフトが残るため。同時に
settings.jsonをsettings.json.tmplに変えている。autoModeの説明文にホームディレクトリの絶対パスが3箇所あり、そこにアカウント名が入る。このリポジトリは public で、これまで同じ文字列を一度も含んでいない。{{ .chezmoi.homeDir }}に置き換えて公開物に残らないようにした。chezmoi execute-templateの出力が現行の~/.claude/settings.jsonと完全に一致することを確認済み。3ファイルともchezmoi diffは空である。CI
7 チェックすべて pass(ShellCheck / bash-guard hook test macOS・Ubuntu / chezmoi apply on macOS / chezmoiscripts test macOS・Ubuntu / pre-commit hook test)。
🤖 Generated with Claude Code