Claude Media
sandbox.credentials.filesで認証情報ファイルをmaskする設定

sandbox.credentials.filesで認証情報ファイルをmaskする設定

sandbox.credentials.filesはサンドボックス内の認証情報ファイルをdenyかmaskで守る設定です。extractやdecode: jwtで部分マスクする方法も扱います。

sandbox.credentials.filesで認証情報ファイルをmaskする設定

sandbox.credentials.filesとは

sandbox.credentials.files は、サンドボックス化されたBashコマンドから特定の認証情報ファイルを守るための設定です。sandbox.credentials オブジェクトの中に置く配列で、各エントリは対象ファイルの path と、"deny" または "mask"mode を持ちます。

deny は単純です。sandbox.filesystem.denyRead と同じ読み取りブロックをそのパスに適用し、サンドボックス内のコマンドはファイルを一切読めなくなります。mask はもう一段階手が込んでいます。LinuxとWSL2では、サンドボックス内のコマンドにセンチネル(sentinel、実データの代わりに見せる身代わりの値)入りのコピーを読ませ、サンドボックスプロキシが許可済みのホストへのリクエストでだけ実値を代入します。macOSではmaskを指定してもファイルごと読み取り不能になります。

sandbox.credentials.files 自体はClaude Code v2.1.187以降で使えます。mode: "mask" はv2.1.221以降が必要です。デフォルトは未設定で、リストしたファイル以外は保護されません。ファイルシステム全体はデフォルトで読めるため、~/.aws/credentials~/.ssh のような認証情報ファイルも、この設定を書かない限りサンドボックス内から素通しで読めてしまいます。

似た名前の設定に sandbox.filesystem.disabled があります。あちらはファイルシステム分離そのものを丸ごと外すスイッチで、credentials.files はその逆に近い立ち位置です。分離は有効にしたまま、特定の認証情報ファイルだけをさらに狭く守る設定です。

denyとmaskはどう使い分けるか

denyはツールごと機能を止めます。認証情報ファイルの中身を渡さないので、ghaws のように読めないと認証できないツールはサンドボックス内でエラーになります。maskはツールを動かし続けたいときの選択肢です。サンドボックス内のプロセスはセンチネルしか見ませんが、そのプロセスが injectHosts に該当するホストへリクエストを送った瞬間だけ、プロキシが実値に差し替えます。

用途denymask
ツールごと機能を止めてよいdenymask-
ツールは動かしつつ実値を渡したくないdeny-mask
対象がディレクトリ / globパターンdeny◎(明示指定)mask✗(自動でdenyへ格下げ)
対象が8MiB超 / UTF-8以外のファイルdeny◎(明示指定)mask✗(自動でdenyへ格下げ)

maskはファイル単位でしか働きません。ディレクトリやglobパターン、8MiBを超えるファイル、UTF-8テキストでないファイルを mode: "mask" で指定しても、安全にマスクできないため自動的に deny へフォールバックします。ディレクトリを守りたいなら、最初から deny エントリとして書きます。

ファイルシステム分離を丸ごと切る filesystem.disabled との関係も、denyとmaskで扱いが違います。denyによるファイル保護はファイルシステム層の一部なので、ファイルシステム分離を切るとその瞬間から効かなくなります。一方、maskエントリは(denyへフォールバックしていない限り)ファイルシステム層と独立に動くので、ファイルシステム分離を切っても保護が残ります。環境変数側のdeny / maskも同様にファイルシステム層とは独立です。

extractとonExtractNoMatchで部分マスクする

extractdecode を両方省略すると、Claude Codeはファイル内容全体を1つのセンチネルで丸ごと置き換えます。設定ファイルの中の1フィールドだけが秘密で、残りはコマンドが読めないと困る、というケースにはこれでは粗すぎます。

extract は少なくとも1つのキャプチャグループを持つ正規表現です。マッチした部分(グループ1)だけをマスクし、ファイルの残りはそのまま解析可能な状態を保ちます。マッチが1件も無かったときの挙動は onExtractNoMatch で決めます。既定の "warn" は警告を出したうえでファイルをそのまま読める状態にし、"deny" は読み取り不能にし、"error" はサンドボックスのセットアップ自体を止めます。秘密情報が無いことが正常な状態なら warn のままでよく、パターンが取りこぼす可能性があるなら deny にして安全側に倒します。

gh の設定ファイルからOAuthトークンだけを取り出して隠す例です。

{
  "sandbox": {
    "credentials": {
      "files": [
        {
          "path": "~/.config/gh/hosts.yml",
          "mode": "mask",
          "extract": "oauth_token:\\s*(\\S+)",
          "onExtractNoMatch": "deny",
          "injectHosts": ["api.github.com"]
        }
      ]
    }
  }
}

maskDuplicatestrue にすると、extractdecode でマスクした値がファイル内の別の場所に生の文字列としてもう一度現れていた場合、そこも一緒に置き換えます。コメント欄に秘密情報を貼り付けてしまったようなケースに効きますが、判定は生の部分文字列一致です。短い値や共通の文字列に使うと関係ない箇所まで巻き込むので、長く高エントロピーな秘密情報に限って使います。maskDuplicatesextractdecode を設定しているときだけ意味を持ち、既定は false です。

JWTを保ったままdecode: jwtでmaskする

extract は正規表現でテキストを切り出すだけなので、JWT(JSON Web Token)のように3つの部分がピリオドで繋がった構造を持つ値には向きません。デコードして中身を読むコードがサンドボックス内で動いているなら、単純な置き換えはそのコードを壊します。

decode: "jwt" はこの問題に対応します。組み込みのパターン、または extract を設定している場合はそのパターンでJWTの候補を探し、各候補が実際にJWTとして検証できるかを確認したうえで、構造的に妥当な偽トークンに置き換えます。デコード処理自体は壊れず、中身だけが差し替わります。

maskClaims を組み合わせると、さらに一段階細かく制御できます。検証済みのJWTのうち、指定したトップレベルのclaimだけをマスクしてペイロードを再構築し、それ以外のclaimはそのまま読める状態にします。有効期限(exp)や発行者(iss)はデバッグのために残しつつ、ユーザー識別子だけを隠す、といった使い方ができます。指定したclaimが1つも見つからなかった場合の挙動も onExtractNoMatch が決めます。

extract・decode・maskClaimsを組み合わせた設定例

JWTを含む認証キャッシュファイルから、有効期限は残しつつユーザー識別子だけを隠す設定です。

{
  "sandbox": {
    "credentials": {
      "files": [
        {
          "path": "~/.cache/myservice/token.json",
          "mode": "mask",
          "extract": "\"access_token\"\\s*:\\s*\"([^\"]+)\"",
          "decode": "jwt",
          "maskClaims": ["sub", "email"],
          "onExtractNoMatch": "deny",
          "injectHosts": ["api.myservice.example"]
        }
      ]
    }
  }
}

injectHostsとtlsTerminateが無いとmaskは動かない

mask による実値の代入は、サンドボックスプロキシがリクエストの中身を書き換えることで成立します。プロキシが中身を見るにはTLSをプロキシ側で終端する必要があるので、sandbox.network.tlsTerminate を設定するか、平文HTTPのテストネットワーク向けに allowPlaintextInject を設定します。これを忘れると、サンドボックス内のコマンドはセンチネルしか見えないまま、そのセンチネルが書き換えられずにサーバーへ届いてしまい、認証だけが静かに失敗します。Claude Codeは起動時にこの設定漏れを警告しますが、警告を見落とすと「なぜか認証が通らない」という遠回りな調査になります。

injectHosts は実値を届けてよいホストの一覧です。未指定なら sandbox.network.allowedDomains に含まれるすべてのホストへのリクエストで実値を代入します。用途が決まっているトークンほど injectHosts を絞り、意図しないホストへ実値が漏れる経路を減らせます。

さらに、maskエントリはユーザー設定・managed設定・--settings フラグからしか有効になりません。リポジトリに含まれる .claude/settings.json.claude/settings.local.jsonmode: "mask" を書いても無視されます。チェックアウトしただけのプロジェクトが、意図せず実認証情報をどこかへ流す設定を持ち込めないようにするためです。

プロジェクト設定とmanaged設定でscopeがどう変わるか

deny エントリは、セッションが読み込むすべての設定スコープから配列としてマージされます。denyはアクセスを狭めるだけなので、どのスコープからも追加でき、他のスコープが追加したdenyを後から取り消せるスコープはありません。

v2.1.246より前は、--setting-sources でプロジェクト設定やローカル設定を除外しても、それらのファイルに書かれた deny エントリは適用されていました。v2.1.246以降は、プロジェクト設定・ローカル設定を除外すると credentials エントリがdenyも含めて一切適用されなくなります。除外の意図がより素直に反映されるようになった変更です。

管理者がmanaged設定で credentials.filesmode: "deny" のエントリを1つでも列挙すると、sandbox.filesystem.disabled はそれ以降managed設定からしか変更できなくなります。開発者が自分の設定でファイルシステム分離を解除しても、そのdenyエントリだけは効き続けるということです。mode: "mask" のエントリは、セットアップ時にdenyへフォールバックしていない限りこの制約(pin)を掛けません。マスクという保護の仕組み自体がファイルシステム層と独立だからです。

managed設定の credentials エントリが検証に失敗したときの扱いも段階的です。pathname、そして mask / deny のどちらかの mode が有効なら、たとえば extract にキャプチャグループが無いような不備があっても、エントリは mode: "deny" へ格下げされて警告付きで動き続けます。mode 自体が不明だったり path / name が無効だったりするエントリは削除されます。この段階的な扱いはv2.1.191以降のもので、それより前は無効なエントリを一律で削除していました。

まとめ

sandbox.credentials.files は、サンドボックス化されたBashコマンドから個別の認証情報ファイルを守る設定です。ツールごと機能を止めてよいなら deny、ツールは動かしつつ実値を渡したくないなら mask を選びます。mask は既定でファイル全体を置き換えますが、extract で部分マスクに絞り、decode: "jwt"maskClaims を足せばJWTの構造を保ったまま特定のclaimだけを隠せます。バージョン要件だけは先に確認しておく必要があります。files 自体はv2.1.187以降、mask はv2.1.221以降、decodemaskClaims はv2.1.224以降、プロジェクト/ローカル設定からの credentials 完全除外はv2.1.246以降です。

チームで管理者が配布する運用ならmanaged設定に書いて filesystem.disabled をpinする効果まで含めて設計し、個人の開発環境ならユーザー設定に書くだけで十分です。マスクという仕組み自体がどこまで情報漏洩対策として頼れるかは、認証情報マスキングは情報漏洩対策になるかで扱っています。環境変数側の mask 設定を含めた基本の設定手順は、Claude Codeの認証情報マスキングをサンドボックスで設定する方法にまとまっています。

よくある質問

mode: "mask"を指定したのに、ファイルが丸ごと読み取り不能になるのはなぜですか

macOSでは、ファイルシステム分離が有効な間、mask エントリは extractdecode が走る前に deny として適用されます。センチネル差し替えとプロキシ経由の実値代入はLinuxとWSL2の挙動です。macOSで部分マスクを試したい場合は、ファイルシステム分離を切った状態でしか extract / decode の効果を確認できません。

injectHostsを空にすると、実値はどこにも届かなくなりますか

injectHosts を書かなければ、sandbox.network.allowedDomains に含まれるすべてのホストへのリクエストで実値が代入されます。届く先をゼロにしたい場合は、そもそも mask ではなく deny を使うのが素直です。

この記事を共有:XはてブLinkedIn