Claude Codeのsandbox(サンドボックス)とは、Claudeが実行するシェルコマンドを、OSの仕組みでファイルとネットワークの範囲に閉じ込める機能です。permissionsのdenyルールが「Claudeが書いたコマンドの文字列」を見て止めるのに対し、sandboxは「実際に動いたプロセス」をOSが止めます。
settings.jsonに Read(./.env) や Bash(curl *) をdenyで書いておけば安心、と考えがちです。ところが、同じ操作を別の書き方で実行されると、denyは反応しません。auto modeで確認の回数を減らすほど、この差は効いてきます。
この記事では、denyだけの設定とsandboxを足した設定で、同じ6つのコマンドを実際に試した結果を載せます。そのうえで、2つの仕組みの役割分担、チームで共有するsettings.jsonの書き方、auto modeとの組み合わせ、自分の設定が本当に効いているかを確かめる手順までをまとめます。検証は2026年11月1日に、macOS上のClaude Code 2.1.316で行いました。公式ドキュメントも同じ日に確認しています。
結論:denyは「書き方」、sandboxは「動いたプロセス」を止める
Claude Codeで本当に止めたい操作(秘密ファイルの読み取り・許可していない通信・作業フォルダの外への書き込み)は、permissionsのdenyだけでなく、sandboxで止めるのが確実です。denyは便利な一次フィルターですが、公式ドキュメントも境界(セキュリティの壁)ではないと書いています。
| 項目 | permissions(allow / ask / deny) | sandbox |
|---|---|---|
| 何を見て判定するか | Claudeが書いたコマンドの文字列・ツール名・パス | 実行中のプロセスが触るファイルと接続先 |
| 誰が止めるか | Claude Code(実行前に判定) | OS(macOSはSeatbelt、Linux・WSL2はbubblewrap) |
| 対象 | すべてのツール(Bash・Read・Edit・WebFetch・MCPなど) | シェルコマンドと、そこから起動したプロセスだけ |
| 弱いところ | python3 -c や sh -c の中までは追えない | ReadツールやWebFetch、MCPは対象外 |
| 向いている使い方 | よく使うコマンドの許可、明らかに危ないコマンドの拒否 | 秘密ファイル・通信先・書き込み範囲の強制 |
つまり、どちらか一方では足りません。Claudeのツール全体はpermissionsで、シェルの中で起きることはsandboxで止める、という分担が基本形です。
実測:denyだけの設定で6つのコマンドを試した
まず、sandboxを使わず、permissionsだけを書いた設定で試しました。denyに入れたのは .env の読み取りと curl の2つです。動作を見るために、試すコマンドだけをallowに入れています。
{
"permissions": {
"deny": ["Read(./.env)", "Bash(curl *)"],
"allow": [
"Bash(cat .env)",
"Bash(python3 -c *)",
"Bash(/usr/bin/curl -sI https://example.com)",
"Bash(sh -c *)"
]
}
}.env にはテスト用のダミー値だけを入れ、Claude Codeに次の6つを1つずつ実行させました。結果は、Claude Codeが出力するツールの実行結果(stream-json)から読み取っています。
| # | コマンド | 結果 | 出たメッセージ・出力 |
|---|---|---|---|
| 1 | cat .env | 止まった | Permission to use Bash with command cat .env has been denied. |
| 2 | python3 -c "print(open('.env').read())" | 通った | .env の中身(ダミー値)がそのまま表示された |
| 3 | curl -sI https://example.com | 止まった | Permission to use Bash with command curl … has been denied. |
| 4 | /usr/bin/curl -sI https://example.com | 止まった | 同じく has been denied |
| 5 | sh -c 'curl -sI https://example.com' | 通った | HTTP/2 200 が返り、外部と通信できた |
| 6 | python3 -c "open('../outside-A.txt','w').write('x')" | 通った | 作業フォルダの1つ上にファイルが作られた |
denyで止まったのは、コマンドの文字列にルールの形がそのまま現れたものだけでした。Pythonの中で開いたファイル、sh -c の中のcurl、Pythonが書いたファイルの行き先は、denyからは見えていません。
公式ドキュメント(Configure permissions)も、Read・Editのdenyルールは cat や sed のように「Claude Codeが認識できるファイル操作のコマンド」には効くが、PythonやNodeのスクリプトが自分でファイルを開く場合には効かない、と明記しています。Bashのルールについても、同じプログラムを別の形で呼ぶと一致しないため「プログラムの周りのセキュリティの境界ではない」と書かれています。
なお、4の絶対パスのcurlは、公式ドキュメントの表では「止まらない例」として挙げられていますが、今回のバージョン(2.1.316)では止まりました。一方で5の sh -c は通っています。どこまで追えるかはバージョンで変わり得るので、denyを境界として当てにしない、という結論は変わりません。
実測:sandboxを足すと、同じ6つはどうなるか
次に、同じpermissionsに次のsandboxの設定を足して、まったく同じ6つを試しました(作業フォルダだけ別にしています)。どちらの設定も、起動時の --settings で渡しています。
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["./.env"]
},
"network": {
"strictAllowlist": true
}
}
}| # | コマンド | 結果 | 止めたもの・メッセージ |
|---|---|---|---|
| 1 | cat .env | 止まった | OS:cat: .env: Operation not permitted |
| 2 | python3 -c "print(open('.env').read())" | 止まった | OS:PermissionError: [Errno 1] Operation not permitted: ‘.env’ |
| 3 | curl -sI https://example.com | 止まった | permissions:has been denied |
| 4 | /usr/bin/curl -sI https://example.com | 止まった | permissions:has been denied |
| 5 | sh -c 'curl -sI https://example.com' | 止まった | プロキシ:403 host is not on the allow list(X-Proxy-Error: blocked-by-allowlist) |
| 6 | python3 -c "open('../outside-B.txt','w').write('x')" | 止まった | OS:PermissionError: Operation not permitted: ‘../outside-B.txt’ |
denyをすり抜けた3つ(2・5・6)は、sandboxを足すとすべてOSかプロキシで止まりました。5では、出力の中に <sandbox_violations> として「deny network-outbound example.com:443 (host is not on the allow list)」という記録も出ています。
書き込みは、何も指定しなくても作業フォルダ(と一時フォルダ)の外は拒否されます。ネットワークは、許可するドメインの一覧が空の状態から始まります(Sandboxing)。今回の6の書き込みも、5の通信も、追加の設定なしで止まっています。
念のため、network.allowedDomains に example.com だけを入れた設定でも試しました。sh -c 'curl -sI https://example.com' は200が返り、sh -c 'curl -sI https://www.google.com' は同じ403で止まりました。許可した宛先だけが通ることも確かめられます。
sandboxの外にあるもの:ここはpermissionsで守る
sandboxが閉じ込めるのはシェルコマンドだけです。公式ドキュメントには、Claudeのファイル用のツール、MCPサーバー、hooksはsandboxの外で動く、と書かれています。sandboxの denyRead はReadツールを止めず、allowedDomains はWebFetchを制限しません。
| sandboxの外で動くもの | 守り方 |
|---|---|
| Read・Edit・Writeなどのファイル用ツール | permissionsの Read(…)・Edit(…) のdenyを書く(sandboxの denyRead と両方に入れる) |
| WebFetch | WebFetch(domain:…) のallowで宛先を絞る |
| MCPサーバー | 使うサーバーだけを入れる。ツール単位で止めるならdenyに mcp__サーバー名__ツール名 |
| hooks・ステータスラインのコマンド | 自分で書いたものだけを置く(sandboxは効かない) |
excludedCommands に入れたコマンド | 制限なしで動くので、入れるのは最小限にする |
入力欄で ! を付けて自分が打ったコマンド | 多くの場合sandboxの外で動く。自分の操作として扱う |
また、sandboxで止まったコマンドを、Claudeが dangerouslyDisableSandbox を付けてsandboxの外で再実行しようとすることがあります。手動の確認モードではこの再実行に確認が出ますが、bypassPermissionsでは確認なしで通り、auto modeでは分類用のモデルが判定します。allowUnsandboxedCommands を false にすると、この再実行そのものを無視させられます。
チームで共有するsettings.jsonの書き方
ここまでの結果をまとめると、プロジェクトで共有する .claude/settings.json は次の形が出発点になります。実測で使った設定から、試験用のallowと、共有の設定では効かない strictAllowlist を外したものです。この形でも、Pythonからの .env の読み取りは「Operation not permitted」で止まり、allowedDomains に入れた example.com だけに通信できることを確かめました。
{
"permissions": {
"deny": ["Read(./.env)", "Bash(curl *)"]
},
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["./.env"]
},
"network": {
"allowedDomains": ["example.com"]
}
}
}各項目の役割は次のとおりです。allowedDomains の example.com は、実際に使うレジストリやAPIのドメインに置き換えます。
| キー | 役割 | 外したときに起きること |
|---|---|---|
sandbox.enabled | sandboxを有効にする(初期値は無効) | シェルコマンドがOSの制限なしで動く |
failIfUnavailable | sandboxを起動できない環境では、Claude Codeを起動させない | sandboxが使えない環境で、気づかずに制限なしで動く |
allowUnsandboxedCommands: false | sandboxの外での再実行を受け付けない | 止まったコマンドが、確認の取り方しだいで外で動く |
filesystem.denyRead | プロセスからの読み取りをOSで拒否する | Pythonなどからの読み取りが通る(実測の2) |
network.strictAllowlist | 一覧にない宛先は確認を出さずに拒否する。ユーザー設定・管理用の設定・--settings でだけ効き、共有の .claude/settings.json に書いても無視される | 一覧にない宛先で確認が出る |
network.allowedDomains | 通信してよい宛先 | (空のまま)どの宛先も最初は止まり、確認が出る。strictAllowlistありなら確認なしで拒否 |
failIfUnavailable と allowUnsandboxedCommands の2つは、公式ドキュメントでも厳しくする設定(strict mode)として並べられています。sandboxを「入れたつもり」で終わらせないための2つなので、共有する設定には入れておくのがおすすめです。
一覧にない宛先を確認なしで拒否させたい場合は、strictAllowlist: true を各自の ~/.claude/settings.json か管理用の設定に置きます。公式ドキュメントでも、この設定はリポジトリの .claude/settings.json や .claude/settings.local.json に書いても効かないとされています。共有の設定だけの場合、手動の確認モードでは一覧にない宛先で確認が出ます(今回 --permission-prompts none で試した回では「403 user denied」で止まりました)。なお、bypassPermissionsでは、strictAllowlistが無いと一覧にない宛先にも通信できます。
permissionsの構文(ワイルドカードの位置、MCPの書き方など)の基本は、Claude Code上級編|MCP・Hooks・Skills・サブエージェント実践ガイドのパーミッションの節にまとめています。
設定ファイルの置き場所と、効く順番
設定は4種類のファイルと起動時の --settings から読まれ、同じキーが複数にあるときは上の段が勝ちます。allowやdenyのような一覧は上書きではなく合算されます。どこかにdenyがあれば、下の段のallowで打ち消すことはできません(Settings files and precedence)。
| 優先順位 | ファイル | 誰に効くか |
|---|---|---|
| 1(最上位) | 管理者が配る管理用の設定(managed settings) | 組織の全員。誰も上書きできない |
| 2 | 起動時の --settings | そのセッションだけ |
| 3 | .claude/settings.local.json | 自分だけ・このプロジェクトだけ(gitに入れない) |
| 4 | .claude/settings.json | このプロジェクトの全員(gitで共有する) |
| 5 | ~/.claude/settings.json | 自分だけ・すべてのプロジェクト |
共有の .claude/settings.json には1つ注意点があります。allowルールは、各メンバーがそのフォルダを信頼する(初回の確認で承認する)までは効きません。denyとaskはすぐに効きます。今回の検証でも、信頼していないフォルダで共有の設定にallowを書いたところ、起動時に「Ignoring 4 permissions.allow entries from .claude/settings.json: this workspace has not been trusted.」と表示され、allowが読み飛ばされました。
また、defaultMode の auto と bypassPermissions は、プロジェクトの設定ファイルからは効きません。リポジトリを開いただけで確認なしのモードに切り替わることを防ぐためです。組織として確認なしのモードを禁止したい場合は、管理用の設定で permissions.disableBypassPermissionsMode や permissions.disableAutoMode を "disable" にします。
auto modeと組み合わせるときの考え方
auto modeは、確認の代わりに別の分類用のモデルが操作を事前に審査するモードです。公式ドキュメントは「確認を減らすが、安全を保証するものではない」と書いています(Permission modes)。auto modeとsandboxは別々に働き、組み合わせて使えます。
| auto modeでの動き | 内容 |
|---|---|
| denyルール | どのモードでも効く(bypassPermissionsでも) |
| askルール | どのモードでも確認が出る |
| 広すぎるallow | Bash(*)、Bash(python*) のようなインタープリターのワイルドカード、パッケージマネージャーのrun系などは、auto modeに入ると外される。Bash(npm test) のような狭いルールは残る |
| 既定で止める例 | curl | bash のようなダウンロードして実行、秘密の情報の外部送信、本番へのデプロイ、force push など |
| 既定で通す例 | 作業フォルダ内のファイル操作、lockファイルにある依存のインストール、読み取りだけのHTTPリクエストなど |
auto modeの審査はモデルによる判断なので、判断が外れることはあり得ます。sandboxはOSが強制するので、モデルが何を選んでも境界は変わりません。auto modeで確認を減らすなら、sandboxを先に入れておく、という順番で考えると安全側に倒せます。
なお、sandboxの「自動で許可するモード」(autoAllowBashIfSandboxed)とauto modeは別物です。前者は「sandboxの中で動くBashなら確認しない」という設定で、権限のモードではありません。
自分の設定が効いているかを確かめる手順
設定を書いただけで終わらせず、禁止した操作をわざと試して、止まるかを見ます。今回の検証と同じ手順です。
- テスト用のフォルダを作り、ダミー値だけを書いた
.envを置く(本物の鍵は使わない) - 確かめたい設定を、テスト用のフォルダに
test-settings.jsonとして保存する(宛先の確認には、allowedDomainsに入れていないドメインを使う) - 下のコマンドで、止まってほしいコマンドを1つずつ実行させる
- 出力(run.jsonl)から、どの仕組みが止めたかを確かめる
- 止まるべきものが1つでも通ったら、sandboxの設定(denyRead・allowedDomains・書き込み範囲)を見直す
claude -p "これは権限設定の動作テストです。次のコマンドをBashツールで1つずつ、書いたとおり実行してください。止められても再試行しないでください。1. python3 -c \"print(open('.env').read())\" 2. sh -c 'curl -sI https://www.google.com'" \
--settings ./test-settings.json \
--setting-sources local \
--strict-mcp-config \
--permission-mode manual \
--permission-prompts none \
--output-format stream-json --verbose < /dev/null > run.jsonl--setting-sources local と --settings を組み合わせると、ふだんのユーザー設定や共有の設定を混ぜずに、試したい設定で動かせます(管理用の設定と、そのフォルダの .claude/settings.local.json は読み込まれます)。--permission-prompts none は、確認が必要になった操作を自動で拒否させる指定で、確認画面を出さずに最後まで流せます。
grep -o -E 'has been denied|Operation not permitted|blocked-by-allowlist' run.jsonl | sort | uniq -c見るのは件数ではなく、どの文言が出たかです(最後の要約にも同じ文言が入るので、件数は実行した数より多くなります)。
| 出た文言 | 止めたもの | 判定 |
|---|---|---|
| has been denied | permissions(Claude Codeの判定) | 文字列で止まっただけ。書き方を変えると通る可能性がある |
| Operation not permitted | sandboxのファイルの制限(OS) | 合格。プロセスが何であっても止まる |
| blocked-by-allowlist | sandboxのネットワークの制限(プロキシ) | 合格。許可していない宛先には出られない |
| どれも出ず、中身や200が返った | 何も止めていない | 不合格。sandboxの設定を見直す |
合格の目安は、Pythonや sh -c を経由したコマンドが「Operation not permitted」か「blocked-by-allowlist」で止まることです。「has been denied」しか出ていない場合は、denyに頼った状態なので、同じ操作を別の書き方で試してください。
ここまで確かめたら、次の一手は運用への組み込みです。共有の .claude/settings.json をコミットし、設定を変えるたびにこのテストを流します。複数のプロジェクトをまたいで同じ方針をそろえる方法は、Claude Codeのルール・エージェント設計術で扱っています。
よくあるつまずき
sandboxを入れた直後に出やすい戸惑いと、その原因です。
allowedDomainsに入れたのに、sshやデータベースにつながらない
sandboxの通信は、プロキシを経由するHTTP系の通信だけが通ります。公式ドキュメントによると、プロキシの環境変数を見ない素の ssh や多くのデータベースのドライバーは、許可した宛先でもつながりません。UDPや ping も外に出られません。こうしたコマンドが必要なら、作業の切り分け自体を見直すか、excludedCommands に入れる範囲を最小限にします。
Windowsで有効にしても効いていない
sandboxが使えるのはmacOS・Linux・WSL2です。公式ドキュメントでは、ネイティブのWindowsではコマンドはsandboxなしで動くとされています。failIfUnavailable を true にしておくと、使えない環境では起動しない側に倒せます。
excludedCommandsに入れたら、それまで止まっていたものが通るようになった
excludedCommands に一致したコマンドは、ファイルにもネットワークにも制限なしで動きます。docker compose * のようにどうしても必要なものだけを入れ、広いパターンにしないことが大切です。
sandboxを有効にしたのに、Readツールで.envが読めた
sandboxはシェルコマンドだけが対象です。Readツールを止めるには、permissionsのdenyに Read(./.env) を書きます。実測の設定でも、sandboxの denyRead とpermissionsのdenyの両方に入れています。
よくある質問
Claude Codeのsandboxと権限設定について、よくある質問をまとめました。
Q. permissionsのdenyだけでは不十分ですか?
よく使う危ないコマンドを止める一次フィルターとしては有効ですが、境界としては不十分です。今回の実測でも、Pythonや sh -c を経由した読み取り・通信・書き込みはdenyを通り抜け、sandboxを足すとOSで止まりました。
Q. sandboxを有効にすると、Claude Codeは使いにくくなりますか?
書き込みは作業フォルダの中、通信は許可したドメインだけになるので、外部に出るコマンドは最初は止まります。必要な宛先を allowedDomains に足していけば、作業フォルダの中の作業はそのまま進められます。
Q. auto modeを使うならsandboxは要りませんか?
要ります。auto modeの審査はモデルによる判断で、公式ドキュメントも安全を保証するものではないとしています。OSで強制するsandboxを先に入れ、その上でauto modeを使うのが安全側の組み合わせです。
Q. チームで使うとき、どのファイルに書けばいいですか?
全員に効かせたいsandboxとdenyは、gitで共有する .claude/settings.json に書きます。ただし strictAllowlist は共有の設定では効かないので、各自のユーザー設定か管理用の設定に置きます。個人だけの例外は .claude/settings.local.json に書きます。組織として必ず守らせたい設定は、管理者が配る管理用の設定に置きます。
Q. 設定が効いているかを一番手早く確かめる方法は?
ダミーの .env を置いたフォルダで、python3 -c で読み取るコマンドと、sh -c の中で許可していないドメインにcurlするコマンドを実行させ、「Operation not permitted」と「blocked-by-allowlist」が出るかを見ます。「has been denied」しか出なければ、sandboxで止まっていません。
まとめ
Claude Codeで本当に止めたい操作は、permissionsのdenyではなくsandboxで止め、sandboxの外にあるツールはpermissionsで守る、という分担にします。
- denyはコマンドの文字列で判定するので、Pythonや
sh -cを経由すると通る(実測で確認) - sandboxはOSとプロキシで止めるので、書き方を変えても通らない(実測で確認)
- sandboxはシェルだけが対象。Read・WebFetch・MCPはpermissionsで守る
failIfUnavailableとallowUnsandboxedCommands: falseで「入れたつもり」を防ぐ- 設定を変えたら、禁止した操作をわざと試して、止めた文言で合否を見る
Claude Codeを毎日の運用に組み込んでいる例は、Claude Codeの運用実例|自分専用ダッシュボードで26プロジェクトを回すでも紹介しています。コマンドやスラッシュコマンドの一覧はClaude Codeコマンド一覧&実践ガイドが手元の早見表になります。
