Claude Codeの「Bashサンドボックス」を実務で使う ── 書き込み拒否の正体と、直った境界・直っていない境界を実機で確かめる

Claude Codeの「Bashサンドボックス」を実務で使う ── 書き込み拒否の正体と、直った境界・直っていない境界を実機で確かめる Claude Code活用

はじめに

Claude Codeには、Bashコマンドを1つずつ承認する代わりに「触っていいファイルとドメインをあらかじめ決めておき、OSレベルで強制する」仕組みがある。/sandboxから有効化するBashサンドボックスだ。ビルドやテストのようなコマンドを、確認プロンプトなしで安全に自動実行させたいときの土台になる。

ただし公式ドキュメント(Configure the sandboxed Bash tool)を読んだだけでは、「うちのプロジェクトのconfig/ディレクトリがなぜか書き込めない」「サンドボックス化したのにcat ~/.ssh/id_rsaが普通に読めてしまった」といった、実際に触らないとわからない挙動までは掴めない。

この記事では、Claude Code v2.1.275のCHANGELOG.mdに載った具体的な修正(「サンドボックス化されたBashコマンドが、hooks/やconfig/という名前のプロジェクトディレクトリに書き込めなかった不具合を修正」)を手がかりに、手元の旧バージョン(v2.1.270)とnpm install --prefixで隔離導入したv2.1.275を実際に叩き比べ、①直った境界②今も直っていない境界③ドキュメントには書いてあるが見落としやすい既定動作、の3つを実測する。あわせて、自動実行フローに組み込む際の設定テンプレートまで組み立てる。

手順:サンドボックスを有効化して境界を確認する

  1. /sandboxをセッション内で実行し、Mode/Overrides/Configの3タブ(Linuxでは依存パッケージ不足時にDependenciesタブも)を確認する。macOSは追加インストール不要(内蔵のSeatbeltを使用)。
  2. Modeタブで「auto-allow」(サンドボックス化できたコマンドは無確認で実行)か「regular permissions」(サンドボックス化されていても通常の確認を維持)かを選ぶ。
  3. 1回限りの検証には--settingsでセッション単位の設定を渡す(.claude/settings.local.jsonを汚さない)。
  4. 既定では、作業ディレクトリ・セッション一時ディレクトリ・--add-dirで追加したディレクトリへの読み書きのみ許可され、それ以外への書き込みは拒否、読み込みは(一部の保護パスを除き)ほぼ制限なしという非対称な設計になっている。この非対称性が今回のつまずき所の核心。

コピペ用プロンプト集

1. 【無料・数秒】手元のバージョンとサンドボックス対応可否を確認する

claude --version

期待結果: 2.1.x (Claude Code)のようなバージョン文字列が出力される。
成功判定: バージョン番号を控えておく。以降のプロンプト2で、v2.1.275以上かどうかで挙動が変わることを比較する。API呼び出しは発生せず、実費は0円。

2. 本命の再現:hooks/・config/という名前のディレクトリへの書き込み

Rails(config/)やgitフック用スクリプト(hooks/)のように、アプリのソースコードとして普通に存在する名前のディレクトリで検証する。以下はGitリポジトリ直下で実行する想定。

mkdir -p hooks config
echo "#!/bin/sh" > hooks/pre-commit.sh
echo "app: demo" > config/settings.yml

claude -p 'Bashツールを使って、./hooks/output.txt というファイルに文字列 test を書き込み、続けて ./config/output.txt にも同じ文字列を書き込んでください。その後 cat で両方の中身を確認し、結果を報告してください。' \
  --settings '{"sandbox":{"enabled":true,"allowUnsandboxedCommands":false}}' \
  --permission-mode bypassPermissions --output-format json

allowUnsandboxedCommands:falseは、サンドボックス内で失敗したコマンドをサンドボックス外へ逃がす「エスケープハッチ」(dangerouslyDisableSandboxパラメータでの再試行)を無効化する設定。これを付けないと、書き込みが拒否されても通常の権限フローに回されて成功してしまい、サンドボックス自体の挙動が見えなくなる。

期待結果(実測):
– 手元のv2.1.270(修正前)で実行した実際の出力:
両方とも書き込みが拒否されました。
`./hooks/output.txt` と `./config/output.txt` への書き込みは、いずれも Bash サンドボックスの
書き込み制限(operation not permitted)によりブロックされました。

(実費 $0.1747。ls hooks configで確認するとoutput.txtは実際に作成されていない)
– npm install @anthropic-ai/claude-code@2.1.275 --prefix /tmp/claude-new --no-saveで隔離導入したv2.1.275で同じコマンドを実行した実際の出力:
両ファイルに sandboxtest275 を書き込み、catで確認しました。出力は
sandboxtest275sandboxtest275(改行なしで連結)で、2つのファイルそれぞれに正しく書き込まれています。

(実費 $0.1729。cat hooks/output.txt config/output.txtで実際に文字列を確認済み)

成功判定: ls hooks configでoutput.txtが作られていれば修正後の挙動。作られていなければCHANGELOG.mdが要求するv2.1.275に届いていない。

3. 対照実験:.git/hooks/は今も拒否されるか

修正が「保護そのものを外した」のではなく「保護対象の判定ミスを直しただけ」であることを確認する。プロンプト2と同じv2.1.275環境で実行する。

claude -p 'Bashツールを使って、./.git/hooks/output.txt というファイルに文字列 test と書き込んでみて、成功したか失敗したかだけ報告してください。' \
  --settings '{"sandbox":{"enabled":true,"allowUnsandboxedCommands":false}}' \
  --permission-mode bypassPermissions --output-format json

期待結果(実測): v2.1.275でも「サンドボックスの書き込み制限により.git/hooks/output.txtへの書き込みは拒否されました(operation not permitted)」と失敗する(実費$0.1271)。
成功判定: ls .git/hooks/output.txtがNo such file or directoryになっていればOK。公式ドキュメントが明記する「Git worktrees」節の「hooks/とconfig(.git内)への書き込みは引き続き拒否される」という記載と一致する。プロジェクト直下のhooks/・config/と、.git内のhooks・configは別物として扱われている。

4. 見落としやすい既定動作:読み込みはほぼ無制限

ダミーの機密情報ファイルで検証する(実際の~/.sshや~/.aws/credentialsは読まない)。

echo "FAKE_SECRET_VALUE_12345" > fakesecret.txt

# (a) 何も設定しない場合
claude -p 'Bashツールで cat ./fakesecret.txt を実行し、出力内容をそのまま報告してください。' \
  --settings '{"sandbox":{"enabled":true,"allowUnsandboxedCommands":false}}' \
  --permission-mode bypassPermissions --output-format json

# (b) 読み取り拒否を明示した場合(パスはフルパスで書くこと。理由は下記つまずき所参照)
claude -p 'Bashツールで cat ./fakesecret.txt を実行し、出力内容をそのまま報告してください。' \
  --settings "{\"sandbox\":{\"enabled\":true,\"allowUnsandboxedCommands\":false,\"credentials\":{\"files\":[{\"path\":\"$(pwd)/fakesecret.txt\",\"mode\":\"deny\"}]}}}" \
  --permission-mode bypassPermissions --output-format json

期待結果(実測):
– (a)はFAKE_SECRET_VALUE_12345がそのまま出力される(実費$0.1268)。公式ドキュメントが明記する「既定の読み込み範囲は、一部の保護パスを除きコンピュータ全体。~/.aws/credentialsや~/.ssh/のような認証情報ファイルも既定では読める」という記載どおり。
– (b)は「このファイルの読み取りはサンドボックスの設定でブロックされています(/path/.../fakesecret.txtが明示的に読み取り拒否リストに入っているため)。catは”Operation not permitted”で失敗しました」と拒否される(実費$0.1284)。

成功判定: (a)でダミー文字列が出力され、(b)で拒否メッセージに変わればOK。サンドボックス化=読み込みも制限、という思い込みは誤りだとこの1往復で確認できる。

5. 実務フロー:自動ビルド・テスト用の安全な既定設定

サンドボックスを常時オンにしつつ、書き込み範囲を広げる場所と、読み込みを塞ぐ認証情報を1つの設定にまとめる。.claude/settings.json(プロジェクト共有)またはユーザー設定に配置する例:

{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "allowWrite": ["~/.cache", "/tmp/build"]
    },
    "credentials": {
      "files": [
        { "path": "~/.aws/credentials", "mode": "deny" },
        { "path": "~/.ssh", "mode": "deny" }
      ],
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

期待結果: npm installやterraformのようにキャッシュディレクトリへの書き込みが必要なコマンドはallowWriteで通り、それ以外の場所への書き込みは拒否、~/.aws/credentialsや~/.sshはプロンプト4で確認した既定の「読み込みほぼ無制限」から外れて読めなくなる。
成功判定: /sandboxのConfigタブ(または--settingsで起動したセッションでcat ~/.aws/credentials相当のBashコマンドを試す)で、実際に拒否されることを確認する。本記事はこの設定ファイル自体の実行確認はしていない(構成要素であるプロンプト2・4は個別に実測済み)ため、導入前に一度手元で試すことを勧める。

活用例

  • CI的な自動実行: ビルド・テスト・lintのように「何をするか事前に分かっているコマンド」を、確認プロンプトなしで回したいとき。allowUnsandboxedCommands:falseと組み合わせれば、サンドボックスが効かない未知のコマンドで安全側に倒せる(逃げ道を塞ぐ)。
  • チームの共有プロジェクト: hooks/やconfig/という名前のディレクトリを持つ一般的なアプリ(Rails、Node.jsのビルド設定など)でも、v2.1.275以降なら名前の衝突を気にせずサンドボックスを使える。
  • 認証情報の多いリポジトリ: .envや~/.aws/credentialsを扱う環境では、既定の「読み込みほぼ無制限」を前提にせず、sandbox.credentialsで明示的に塞ぐ設計が要る。

注意点(つまずき所・限界)

  • sandbox.credentials.filesのパスはフルパスで書く: --settingsのCLIフラグ経由で相対パス(./fakesecret.txt)を指定したところ、拒否ルールが一切効かなかった(プロンプト4の検証中に発見)。フルパスに変えると意図どおり拒否された。公式ドキュメントは./の解決先を「プロジェクト設定ならプロジェクトルート、ユーザー設定なら~/.claude」と説明しているが、--settingsフラグ経由の場合にどちらとして扱われるかは明記が無く、今回の実測結果からは少なくとも作業ディレクトリには解決されていないと判断できる。設定ファイル(.claude/settings.jsonなど)経由での挙動までは検証していない。
  • GitHub Issue #56331(open、2026-05-05作成): 「Railsのconfig/ディレクトリがサンドボックスのdenyWithinAllowに自動で入り、git checkoutが壊れる」という、今回検証した書き込み拒否と同根の報告。今回の実機検証でv2.1.275では単純な書き込み拒否は解消していることを確認したが、このIssueが報告する「ブランチ切り替えを壊す」という症状そのものは検証していない。Issueは記事公開時点でopenのまま。
  • GitHub Issue #93173(open、2026-09-09作成): .claude/skillsや.claude/hooksのようなサンドボックスの保護パス自体は仕様どおり正しく機能している一方、それらのパスをgit管理下に置いているリポジトリではgit checkout時にサンドボックスが書き込みを拒否し、作業ツリーがHEADと食い違う状態になりうるという報告。.claude配下をバージョン管理する運用では要注意。
  • GitHub Issue #93464(open、2026-09-10作成): .gitはサンドボックスの書き込み許可対象のはずだが、git push --set-upstreamが.git/configのロックファイル書き換え(create-then-rename方式)で失敗する報告がある。「.gitは書き込み許可されている」という理解だけでは、すべての書き込みパターンをカバーできない。
  • 今回の実験はすべてmacOS(Seatbelt)・非対話-p実行での実測。Linux/WSL2(bubblewrap)や対話セッションでの挙動は未検証。

まとめ

Bashサンドボックスは「書き込みは狭く、読み込みは広く、境界の判定はときどき名前だけで誤爆する」という設計だと分かれば、過信も過小評価もしないで使える。今日からできる最初の一歩は、プロンプト1でバージョンを確認し、v2.1.275に届いていなければプロンプト2で自分のプロジェクトのhooks/・config/ディレクトリが影響を受けないか確かめることだ。


出典: Configure the sandboxed Bash tool、CHANGELOG.md、Releases、GitHub Issue #56331・#93173・#93464(いずれもopen)

Comments

Copied title and URL