Claude Media
allowWrite/denyWriteでkubectl・terraformの書き込み範囲を調整する

allowWrite/denyWriteでkubectl・terraformの書き込み範囲を調整する

sandbox.filesystem.allowWrite/denyWrite/allowReadで書き込み・読み取り範囲をパス単位で調整する方法と、denyWriteが許可領域の内側にも効く仕組みを扱います。

Claude Codeのサンドボックス化されたBashツールは、既定だと作業ディレクトリと一時ディレクトリの外への書き込みを止めます。kubectlやterraformのようなサブプロセスがkubeconfigやstateファイルへ書き込もうとすると、この既定のままでは失敗します。sandbox.filesystem.allowWrite・denyWrite・allowReadは、書き込みと読み取りの範囲をパス単位で調整する設定です。読み取りのルールが重なったときはより狭いパスを指すルールが勝ち、書き込み側ではdenyWriteが他の設定で書き込み可能になっている領域の内側にも効きます。

allowWriteでkubectl・terraformの書き込み範囲を広げる

既定の書き込み範囲は、現在の作業ディレクトリとそのサブディレクトリ、--add-dir・/add-dir・permissions.additionalDirectoriesで追加したディレクトリ、そして$TMPDIRが指すユーザーごとの一時ディレクトリに限られます。kubectl・terraform・npmのようなサブプロセスがこの範囲の外へ書き込む必要があるときは、sandbox.filesystem.allowWriteで特定パスへのアクセスを許可します。

{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "allowWrite": ["~/.kube", "/tmp/build"]
    }
  }
}

この設定で、kubectlはkubeconfigを更新でき、ビルドは/tmp/build配下に書き込めます。パスはOSのサンドボックス境界で強制されるため、コマンドが起動する子プロセスも含めて範囲が適用されます。公式ドキュメントは、ツールが特定の場所への書き込みだけを必要とする場合、この方法をexcludedCommandsでツールをサンドボックスごと除外するより推奨される選択肢と位置づけています。強制終了で残った空ファイルが設定の保存を妨げる場合は、stale sandbox mask files警告の消し方を参照してください。macOSでcdした先々に空の.claude/.cc-writesが残る件は、.cc-writesが増える原因と掃除の手順にあります。

設定ファイルを書かずに範囲を足す口が--add-dirで、どの設定ファイルを読むかは--setting-sourcesで絞れます。v2.1.289のヘルプには次のように載っています。

claude --help | grep -A1 -E "^  --(add-dir|setting-sources)"
  --add-dir <directories...>            Additional directories to allow tool
                                        access to
--
  --setting-sources <sources>           Comma-separated list of setting sources
                                        to load (user, project, local).

--setting-sourcesで外した設定ファイルについては、そのsandbox.filesystemの項目と、Editの権限ルール、Readの拒否ルールがサンドボックスの構築時に無視されます(v2.1.246以降)。allowWriteなどの一覧を実行中のセッションで編集した場合は、次のサンドボックスコマンドから新しいパスで動きます。

作業ディレクトリがlinked git worktree(git worktree addで作った作業ツリー)のときは、既定の書き込み範囲にもう1つ例外があります。サンドボックスはメインリポジトリが共有する.gitディレクトリへの書き込みも許可し、git commitがrefとインデックスを更新できるようにします。ただしその.gitディレクトリの中のhooks/とconfigへの書き込みは、この例外の対象外として拒否されたままです。

パスの解釈は先頭の記号で決まります。/から始まると絶対パス、~/はホームディレクトリからの相対パス、./または無指定はプロジェクト設定ならプロジェクトルート、ユーザー設定なら~/.claudeが起点になります。絶対パスは//pathの書き方でも解決できます。

末尾のスラッシュは取り除かれるため~/.kubeと~/.kube/は同じ扱いで、末尾の/**も除去されるので~/build/**と~/buildは同じ範囲を指します。v2.1.224より前は末尾のスラッシュがそのままサンドボックスへ渡され、スラッシュ付きで書いたdenyRead・denyWriteの下のパスを読み書きできる場合がありました。この記法はRead・Edit権限ルール(/pathがプロジェクト相対、//pathが絶対)とは基準が逆です。sandbox.filesystem側に/pathと書くと、そのまま絶対パス扱いになります。

denyWriteで書き込みを狭める

allowWriteとは逆に、sandbox.filesystem.denyWriteは特定パスへの書き込みを拒否します。対象になるのは、他の設定で書き込み可能になっている領域の内側のパスも含みます。

{
  "sandbox": {
    "filesystem": {
      "denyWrite": ["/etc", "/usr/local/bin"]
    }
  }
}

システム設定や$PATH上のバイナリへの書き込みを止めたいときに使う設定です。allowWrite・denyWriteの配列はどちらも、セッションが読み込むユーザー設定・プロジェクト設定・ローカル設定・管理設定のすべてからマージされます。Edit権限ルールの許可エントリはallowWriteに、拒否エントリはdenyWriteに、それぞれ自動で加算されます。

読み取りは狭いルールが勝ち、denyWriteは許可領域の内側にも効く

denyWrite・denyReadで範囲を拒否し、allowReadで拒否領域の中の特定パスだけ読み取りを再許可できます。読み取りのルールが重なったときは、より狭いパスを指すルールが優先されます。たとえばCIキャッシュ用のディレクトリだけ、ホームディレクトリの拒否から再度開けたい場合は次のように書きます。

{
  "sandbox": {
    "filesystem": {
      "denyRead": ["~/"],
      "allowRead": ["~/ci-cache"]
    }
  }
}

重なり方は3パターンあり、広い許可の中でも個別の拒否は飲み込まれません。

重なり

読み取りルールが重なったときの結果

  • 拒否の中を狭く開ける

    denyReadが~/、allowReadが~/projectsなら、~/projectsは読めます。ホームディレクトリの残りは塞がれたままです。

  • 広い許可の中を狭く塞ぐ

    allowReadが~/、denyReadが~/.envなら、~/.envだけが塞がれ、それ以外は読めます。

  • ワイルドカードの拒否も同じ

    denyReadが~/**/.envなら、ホームディレクトリ配下のすべての.envが塞がれ、残りは読めます。

macOSではv2.1.236より前に、ワイルドカードのdenyReadが一致したパスを、広いallowReadが再び開けてしまう挙動がありました。

ワイルドカードの扱いはプラットフォームで割れます。denyRead・allowReadはワイルドカード(*)がどのプラットフォームでも使え、LinuxとWSL2では一致する具体パスへ展開されます。一方allowWrite・denyWriteはmacOSでだけワイルドカードが機能します。LinuxとWSL2では*・?・[を含むエントリを設定してもエラーにならず、そのエントリが効果を持たないだけです。Edit権限ルールから加算されるパスにも同じ制限がかかり、/sandboxのConfigタブにはワイルドカードを含むEdit・Readルールへの警告が出ます。

allowWriteでも越えられない保護パス

許可された領域の中でも、サンドボックスは設定やコードの読み込み元になるファイルへの書き込みを拒否します。そこを書き換えられると、コマンドが自分で権限を広げたり、サンドボックスの外で動くフックやMCPサーバーを足したりできてしまうためです。対象は4つのグループです。

  • 作業ディレクトリとその上位: .claudeの設定ファイル、.claude/skills・agents・commands・hooks、.mcp.json
  • 作業ディレクトリのみ: .bashrc・.zshrcなどのシェル起動ファイル、.gitconfig、.vscode・.idea、.git内のhooksとconfig
  • 作業ディレクトリをbareリポジトリに変えうるファイル: 最上位のHEAD・objects・refs
  • ~/.claude(CLAUDE_CONFIG_DIRの指す先): 中身の大半と、~/.claude.json・.credentials.json

LinuxとWSL2では、サンドボックスコマンドの実行中に最上位へ現れたHEADファイルやobjects・refsディレクトリが削除されます。保護された設定ファイルのパスにシンボリックリンクが現れると、次のコマンドから、そのリンク先への書き込みも拒否されます。

保護の例外はありません。allowWriteエントリや対象パスをカバーするEdit許可ルールがあっても解除されず、外せるのはfilesystem.disabledだけです。これはファイルシステム分離そのものを全パスに対して無効化する設定なので、個別パスの保護だけを解除する経路はありません。

リポジトリの設定に書いたallowWriteが効かないとき

プロジェクトの.claude/settings.jsonにallowWriteを書いたのに書き込めない場合は、サンドボックスが管理者必須の状態になっている可能性があります。管理設定か--settingsフラグでallowUnsandboxedCommandsをfalseにすると、この状態になります。管理設定でallowManagedDomainsOnlyをtrueにした場合も同じです。

管理者必須のあいだ、.claude/settings.jsonと.claude/settings.local.jsonの次の項目は無視されます(v2.1.285以降)。

無視される項目無視される範囲
excludedCommands無視される範囲すべてのエントリ
filesystem.allowWrite・Edit許可ルール・permissions.additionalDirectories無視される範囲サンドボックスコマンドに与える書き込み権限(Claudeのファイルツール側は従来どおり)
filesystem.allowRead無視される範囲管理設定・--settings・ユーザー設定が読み取りを拒否したパス以下を再び開けるエントリ

permissions.blockReadsOutsideWorkingDirectoriesが有効なあいだも、リポジトリ設定のallowWriteはマージから外れます。必要なエントリは~/.claude/settings.jsonか--settings、組織の運用なら管理設定に置きます。allowWriteには管理専用のロックがなく、ユーザー設定の分はそのまま有効です。読み取り側のロックはallowManagedReadPathsOnlyの範囲にまとまっています。

書き込みが失敗したときの切り分け

kubectlやgitが書き込みを拒否されたときは、まず/sandboxを実行してConfigタブを開きます。保護パスの大半が、自分で設定したdenyWriteエントリと混ざってDenied within allowedの項目に並びます。

/sandbox
手順

拒否された書き込みの原因を絞る順番

  1. 1

    保護パスか、自分のdenyWriteかを分ける

    Deniedの一覧に載っていて、自分で書いたdenyWriteやEditの拒否ルールでもないなら、保護パスです。allowWriteでは開かないので、別のパスへ逃がすか、その操作だけをサンドボックスの外で行います。

  2. 2

    自分のdenyWriteなら、設定を見直す

    denyWriteはallowWriteで開けた領域の内側でも効きます。意図して塞いだパスでなければ、該当エントリを外します。

  3. 3

    どちらでもなければ書き込み範囲の外

    作業ディレクトリ・追加ディレクトリ・一時ディレクトリのどれにも入っていないパスなので、allowWriteに足します。足したのに効かないときは、リポジトリ側のallowWriteが無視される管理者必須の状態を疑います。

  4. 4

    gitがunable to unlink oldで落ちたとき

    git mergeやgit checkoutは、書き込みを拒否されたファイルを置き換えようとすると、この名前のエラーで失敗します。LinuxとWSL2ではエラーの末尾がRead-only file systemになります。原因は上の3つのどれかで、Claudeがサンドボックス外での再実行を提案することもあります。allowUnsandboxedCommandsをfalseにしていると提案が出ないため、別のターミナルで自分で実行します。

excludedCommandsやfilesystem.disabledとの使い分け

書き込み範囲を広げる手段はallowWriteだけではありません。目的ごとの違いは次のとおりです。

使い分け

書き込みを広げる3つの手段

  • allowWrite

    特定パスへの書き込みだけを許可します。OSレベルで強制され、コマンドはサンドボックスの中に留まります。

  • excludedCommands

    マッチしたコマンドをサンドボックスの外で丸ごと実行します。通常の権限フローに戻るだけの便宜で、セキュリティ境界ではありません。

  • filesystem.disabled

    sandbox.filesystem.disabledはファイルシステム分離ごと外します。ネットワーク分離は残りますが、読み書きは全開放になります。

excludedCommandsは、コマンド全体が条件に一致したときだけサンドボックスの外に出ます。次の形を含む呼び出しはサンドボックス内に留まり続けます。

  • sudo・eval・xargsで始まるコマンド
  • 呼び出しのどこかにあるcd・pushd・popd
  • コマンド置換・サブシェル・ifやforのような制御構文
  • ファイルへのリダイレクト(2>&1のようなファイル記述子の複製は除く)
  • 変数から取り出したコマンド名

呼び出し内のコマンドがすべて一致する必要もあります。docker *というエントリだけではnpm ci && docker build .のようなワンライナーは対象外のままです。書き込み場所だけを広げたい場面でallowWriteが優先される理由の1つはここにあります。効かないことが知られているケースはexcludedCommandsの既知のバグにまとまっています。

管理設定で配布しても、開発者は~/.claude/settings.jsonに自分のexcludedCommandsエントリを追記でき、それがそのままマージされます。管理者が意図した境界を保ちたいなら、対象をallowWriteで絞り込む運用に寄せ、excludedCommandsのリストは狭く保ちます。

allowWriteを広げるときに確認すること

allowWriteで書き込み範囲を広げるときは、その分ネットワーク側の許可まで緩めていないかを合わせて確認します。公式ドキュメントは、ファイルシステム分離とネットワーク分離は独立した層であり、一方を広げるときにもう一方の制限が実質的に打ち消されていないかを確認するよう注意を促しています。

ネットワーク分離が働いていない状態で書き込み範囲だけを絞っても、SSH鍵のような機微なファイルを外部へ持ち出す経路が別に残ることがあります。認証情報ファイルを狙うなら、読み取り側のdenyReadでの遮断も組み合わせます。

まとめ

allowWrite・denyWrite・denyRead・allowReadはスコープがAny fileで、どの設定ファイルに書いても有効です。チーム全員に効かせたい設定は、リポジトリの.claude/settings.jsonでなく、ユーザー設定か管理設定に置くほうが確実です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/sandboxing
  2. code.claude.com/docs/en/settings-reference
この記事を共有:XはてブLinkedIn