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

既定は未設定で、リストしたファイル以外は保護されません。組み込みの認証情報denyリストもありません。サンドボックスの既定の読み取り範囲はマシンのほぼ全体なので、~/.ssh や ~/.aws/credentials もこの設定を書かない限りコマンドから読めます。

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

ファイルごとにdenyとmaskのどちらを書くか

選ぶ基準は「そのファイルを読ませないとツールが壊れるか」です。壊れてよいならdeny、動かし続けたいならmaskになります。

くらべる

denyとmaskの違い

読ませない

deny

sandbox.filesystem.denyRead と同じ読み取りブロックをパスに掛けます。ファイルもディレクトリも指定できます。maskでは扱えないパスも、denyなら指定できます。

身代わりを読ませる

mask

LinuxとWSL2では、サンドボックス内のコマンドにセンチネル(sentinel、実データの代わりに見せる身代わりの値)入りのコピーを読ませます。プロキシが許可済みホストへのリクエストでだけ実値に差し替えるので、gh のようなツールは認証を保ったまま動きます。

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

maskを動かすまでの手順と確認方法

maskは設定を1行足すだけでは動きません。実値の差し替えはサンドボックスプロキシがリクエストの中身を書き換えて成立するので、前提が3つあります。

手順

maskが効くまでに必要な設定

  1. 1

    プロキシにTLSを終端させる

    sandbox.network.tlsTerminate を {} で設定します。v2.1.199以降で使える実験的な設定で、ユーザー設定・managed設定・--settings フラグからしか効きません(リポジトリの設定では有効にできません)。平文HTTPのテストネットワークだけは、代わりに sandbox.credentials.allowPlaintextInject を使います。

  2. 2

    実値を届ける先を許可ドメインに入れる

    injectHosts に書いたホストは、sandbox.network.allowedDomains でも通る必要があります。injectHosts を省くと、allowedDomains に含まれるすべてのホストへのリクエストで実値が差し替わります。用途が決まっているトークンほど injectHosts を絞ります。

    IPv6アドレスは書き方が揃いません。allowedDomains には "[::1]"、injectHosts には角括弧なしの "::1" と書きます。合わないと、claude doctor が「Sandbox credential injectHosts entries can never match their destination」と警告します(v2.1.229以降)。

  3. 3

    信頼できる設定スコープに書く

    mask エントリは、ユーザー設定・managed設定・--settings フラグからしか有効になりません。リポジトリの .claude/settings.json や .claude/settings.local.json に書いても無視されます。maskは実認証情報をどこかへ送る権限をプロキシに与える設定なので、チェックアウトしただけのプロジェクトには持ち込ませない作りです。

TLS終端を忘れた場合、コマンドにはセンチネルしか見えないままですが、そのセンチネルが書き換えられずにサーバーへ届き、認証が失敗します。Claude Codeはこの設定漏れを起動時に報告します。

3つを満たした最小の設定がこちらです。置き場所は ~/.claude/settings.json です。gh のOAuthトークンだけを ~/.config/gh/hosts.yml から切り出して隠します。

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

効いているかどうかは、サンドボックス内のコマンドで cat ~/.config/gh/hosts.yml を実行させて確かめます。LinuxとWSL2ではトークンの位置にセンチネルが出ます。

1フィールドだけ隠したいときのextractとonExtractNoMatch

extract と decode を両方省くと、Claude Codeはファイル内容全体を1つのセンチネルに置き換えます。中身が裸のトークン1つだけのファイルなら、これで足ります。設定ファイルの1フィールドだけが秘密で、残りはツールが解析できないと困るなら、extract で範囲を絞ります。

extract は少なくとも1つのキャプチャグループを持つ正規表現です。グループ1にマッチした部分だけをマスクします。上の oauth_token:\s*(\S+) を、ダミーのhosts.ymlに当てた結果は次のとおりです。

入力:  oauth_token: gho_FAKE0123456789abcdef
group1: gho_FAKE0123456789abcdef

oauth_token: というキーと周りのYAMLは残り、値の部分だけが差し替わります。パターンがどこにもマッチしなかった場合の挙動を決めるのが onExtractNoMatch です。

マッチ0件のとき

onExtractNoMatchの3つの値

  • warn(既定)

    エントリをスキップし、サンドボックス内のコマンドは実ファイルをそのまま読めます。認証情報が最初から無いかもしれないファイルに向きます。

  • deny

    ファイルを読み取り不能にします。秘密は入っているのにパターンが取りこぼすかもしれない、というファイルは安全側のこちらです。

  • error

    設定を直すまでサンドボックスのセットアップ自体を止めます。deny の読み取りブロックが実際には効かない状況では、deny も error として扱われます。ファイルシステム分離を無効にしている場合と、sandbox.filesystem.allowRead がそのパスを再び開いている場合が該当します。

maskDuplicates: true を付けると、マスクした値がファイル内の別の場所に生の文字列として現れたとき、そこも置き換えます。コメント欄に貼り付けたトークンの対策です。判定は生の部分文字列一致なので、短い値や一般的な文字列に使うと関係ない箇所まで巻き込みます。長く高エントロピーな秘密に限ります。extract か decode を設定したときだけ意味を持ち、既定は false です。

JWTの構造を保ったままマスクするdecode: jwt

JWT(JSON Web Token)はピリオドで3つの部分を繋いだ構造をしており、デコードして中身を読むコードがサンドボックス内にあると、文字列を単純に置き換えただけでは壊れます。extract は正規表現でテキストを切り出すだけなので、この用途には届きません。

decode: "jwt" を付けると、Claude Codeは組み込みのパターン(extract があればそのパターン)でJWTの候補を探し、各候補がJWTとして検証できるかを確かめ、構造的に妥当な偽トークンに置き換えます。検証できた候補がなければ、onExtractNoMatch が結果を決めます。

maskClaims を足すと、検証済みJWTのうち指定したトップレベルのclaimだけをマスクし、ペイロードを組み直します。exp や iss はデバッグ用に読める状態で残し、sub や email だけを隠せます。maskClaims は decode が前提です。

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

JWTを含む認証キャッシュファイルから、有効期限は残しつつユーザー識別子だけを隠す設定です。extract が access_token の値を切り出し、decode がそれをJWTとして検証します。injectHosts のホストは allowedDomains にも入れ、tlsTerminate も有効にしてあります。置き場所は ~/.claude/settings.json です。

{
  "sandbox": {
    "enabled": true,
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["api.myservice.example"]
    },
    "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"]
        }
      ]
    }
  }
}

macOSではmaskがdenyとして働く

macOSではmaskエントリがsentinelコピーを作りません。コマンドはファイルを一切読めず、それを前提にするツールはサンドボックス内で動きません。効果は deny と同じです。

macOSでは、ファイルシステム分離が有効な間、パターンを走らせる前にmaskが deny として適用されます。onExtractNoMatch の結果が出るのは、分離を無効にしたときだけです。

分離を無効にしても、macOSの読み取りブロックは維持されます。2つの記述を合わせると、分離を無効にした場合はパターンが走り、マッチすればファイルは読めないままです。マッチしなければ onExtractNoMatch に従い、既定の warn なら実ファイルが読めます。実機での挙動は cat ~/.config/gh/hosts.yml の結果で確かめられます。

設定の置き場所で効き方がどう変わるか

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

--setting-sources で設定元を除外したときの扱いは、除外する対象で変わります。

  • プロジェクト設定・ローカル設定を除外: credentials エントリはdenyも含めて一切適用されません
  • ユーザー設定を除外: ~/.claude/settings.json のdenyは適用されたままです。ファイルのmaskは制限として残りますが、プロキシに実値の差し替えを許可する効力は失います。環境変数のmaskは捨てられます

管理者がmanaged設定の credentials.files に mode: "deny" を1つでも列挙すると、sandbox.filesystem.disabled はmanaged設定からしか変えられなくなります。開発者が自分の設定でファイルシステム分離を外しても、そのdenyは効き続けます。有効な mask エントリはこの固定(pin)を掛けません。起動時に deny へフォールバックしたmaskエントリも同じです。ディレクトリなどmaskできないパスで固定したいときは、managed設定に明示的な deny として書きます。

ファイルシステム分離を無効にしたときは、maskとして適用されたエントリは有効のままです。deny にフォールバックしたエントリは効きません。

managed設定のエントリが検証に落ちたときは、扱いが段階的です。path と、mask または deny の mode が有効なら、extract にキャプチャグループが無いといった不備があっても、エントリは deny に格下げされ、警告付きで動き続けます。格下げされたエントリは明示的なdenyと同じくpinを掛けます。mode が不明、または path が無効なエントリは削除されます。

バージョンごとに使えるもの

使える項目はバージョンで段階的に増えています。環境変数側のmaskは credentials.envVars という別の配列で、v2.1.199以降です。ファイル側とは版が違います。

バージョン

sandbox.credentialsの追加された順番

  1. v2.1.187sandbox.credentials の導入

    認証情報ファイルと秘密の環境変数を、サンドボックス内のコマンドから読ませない設定として追加されました。この時点で使えるのは deny です。

  2. v2.1.191managed設定の検証

    managed設定の credentials エントリが検証に落ちたときの項目単位の扱いが入りました。v2.1.221より前は、無効なエントリはすべて削除されます。

  3. v2.1.221ファイルのmask

    mode: "mask" に加えて、extract・onExtractNoMatch・maskDuplicates・injectHosts がファイル側で使えるようになりました。

  4. v2.1.224JWT対応

    decode と maskClaims、およびdecodeが絡む onExtractNoMatch が使えるようになりました。

  5. v2.1.246除外した設定元の扱い

    プロジェクト設定・ローカル設定を --setting-sources で除外すると、そこに書かれた credentials が適用されなくなります。

  6. v2.1.257残骸ファイルの警告

    /doctor が、強制終了したセッションの置き去りにしたmask用ファイルを警告するようになりました。

claude --version で手元のバージョンを見たうえで、使いたい項目の行を照らし合わせます。v2.1.287の claude --help には --settings と --setting-sources が出ます。設定元を絞った動作確認に使えます。

まとめ

macOSではmaskを書いてもdenyと同じ効果しか得られません。maskを選ぶ意味があるのはLinuxかWSL2で、TLS終端をユーザー設定以上のスコープに置けるときです。

マスクが情報漏洩対策としてどこまで頼れるかは認証情報マスキングは情報漏洩対策になるかに、環境変数側を含む基本手順はClaude Codeの認証情報マスキングをサンドボックスで設定する方法にあります。

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