Claude Codeの認証情報マスキング(mask)をサンドボックスで設定する方法
Claude Codeのmaskは、認証情報を偽の値に置き換えて通信だけ通す設定です。sandbox.credentialsのdenyとの違いと、JWT・AWS SigV4の設定を示します。
Claude Codeのサンドボックスは、sandbox.credentialsでファイルと環境変数の認証情報を保護します。mode: "deny"は読み取りを拒否します。mode: "mask"は、コマンドに見せる値を偽のセンチネルに置き換え、許可済みホストへの通信でだけ実際の値に戻します。環境変数のmaskはv2.1.199以降、ファイルのmaskとAWS SigV4の再署名はv2.1.221以降、JWTのデコードやawsPairsはv2.1.224以降の機能です。この記事は、どちらを選ぶかの判断と、maskを書いたのに保護されないケースを中心に設定例で確認します。
denyとmaskはどこが違うのか
サンドボックスの既定では、書き込みは作業ディレクトリ、--add-dirなどで追加したディレクトリ、ユーザーごとの一時ディレクトリに絞られる一方、読み取りは~/.aws/credentialsや~/.ssh/を含めコンピューター全体がほぼ許可されています。sandbox.credentialsに組み込みの拒否リストはないので、列挙したファイルと変数だけが保護対象です。
deny と mask の違い
deny
ファイルの読み取りを拒否し、環境変数は実行前に外します。ghやnpmのように認証情報で通信するツールは、認証できず動かなくなります。
sandbox.credentialsが効くのは、サンドボックス化されたBashコマンドだけです。フックやMCPのstdioサーバーを含む全サブプロセスから、Claude Codeが認証情報と認識する変数を外したい場合は、環境変数CLAUDE_CODE_SUBPROCESS_ENV_SCRUBを使います。サンドボックス設定全体の書き方はClaude Codeの設定完全ガイドにあります。
どのツールにどちらを使うか
判断の軸は、そのツールが認証情報を使って実際に通信する必要があるかどうかです。
| シーン | 選ぶ設定 | 理由 |
|---|---|---|
| SSH秘密鍵のように、コマンドに触らせたくない秘密 | 選ぶ設定deny | 理由読み取り自体を拒否する。鍵を使うsshなどもサンドボックス内では使えなくなる |
ghやnpmなど、認証情報で通信するツール | 選ぶ設定mask | 理由許可ホストへは実値で届く |
| AWS CLIなど、SigV4で署名するツール | 選ぶ設定mask(両方のキー。変数名が定番でなければawsPairs) | 理由署名の再計算まで自動で行われる |
| JWTを内部でデコードするツール | 選ぶ設定mask + decode: "jwt" | 理由構造が本物と同じ偽トークンでデコードが動く |
denyから始めて、動かなくなったツールだけmaskへ移す進め方もあります。maskにはTLSを終端する設定などの追加設定が要るためです。
環境変数をマスキングする設定
環境変数のmaskにはnetwork.tlsTerminateが必要です。プロキシは暗号化通信の中身にあるセンチネルを書き換えるので、TLSを自分で終端しなければなりません。設定を忘れると、センチネルがそのままサーバーへ届いて認証が失敗します。平文HTTPのテスト用ネットワークでは、代わりにallowPlaintextInjectを使えます。実値は漏れず、Claude Codeは起動時にこの設定漏れを報告します。
{
"sandbox": {
"enabled": true,
"network": {
"tlsTerminate": {},
"allowedDomains": ["*.github.com", "registry.npmjs.org"]
},
"credentials": {
"envVars": [
{ "name": "GH_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] },
{ "name": "NPM_TOKEN", "mode": "mask" }
]
}
}
}GH_TOKENはinjectHostsでapi.github.comだけに絞られます。NPM_TOKENはinjectHostsがないので、allowedDomainsに列挙した全ホストへ実値が届きます。差し替えの対象はヘッダーとリクエスト本文です。
injectHostsにIPv6アドレスを書くときは、allowedDomainsと表記が変わります。allowedDomainsは"[::1]"のように角括弧付き、injectHostsは"::1"のように括弧なしの正規の圧縮形です。表記が違うと実値は注入されません。v2.1.229以降のclaude doctorは、宛先に一致しないinjectHostsを警告します。
ファイルをマスキングする設定
ファイルのmaskは、LinuxとWSL2ではセンチネルに置き換えたコピーを読ませ、外部通信の時点でプロキシが実値に差し替えます。macOSではコピーを作らず、指定したファイルがそもそも読めなくなります。
{
"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"]
}
]
}
}
}extractの正規表現は、キャプチャグループ1が捕まえた範囲だけをセンチネルにします。hosts.ymlはghがパースできる形のまま残ります。省略するとファイル全体が1個のセンチネルになるので、単一の秘密だけを持つファイル向けです。extractにはキャプチャグループが最低1つ要ります。
maskは1ファイルずつ指定する仕組みです。ディレクトリ・globパターン・8MiB超のファイル・UTF-8以外のテキストは安全にマスクできず、自動でdenyにフォールバックします。ディレクトリを守りたいときは、最初からdenyで書きます。
JWTや構造化された値を部分的にマスキングする
decode: "jwt"を指定すると、Claude Codeは値がJWT(JSON Web Token)であることを検証して、構造が本物と同じ偽トークンに置き換えます。サンドボックス内でトークンをデコードするコードは動き続けます。maskClaimsを添えると、指定したトップレベルのクレームだけを個別に隠し、ほかのクレームは読める状態で残ります。
DATABASE_URLのような構造化された値にはextractが使えます。接続文字列全体は残して、パスワード部分だけを置き換えられます。
decodeとextractの扱いは、環境変数とファイルで違います。
- 環境変数:
decode: "jwt"はextractと併用できません。extractとdecodeはどちらもv2.1.224からの対応です - ファイル:
extractで候補を絞ってからdecode: "jwt"を組み合わせられます。extractはv2.1.221から使えていました
AWSのSigV4署名とマスキングを両立させる
AWSのリクエストは認証情報そのものでなく、それを元にしたSigV4署名で認証します。値をただマスクすると署名の元の値とセンチネルが食い違い、リクエストが失敗します。AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEYは、必ず両方をマスクします。
プロキシがSigV4リクエストを扱う流れ
- 1
センチネルで署名される
サンドボックス内のツールは、センチネルの値でリクエストに署名します。
- 2
アクセスキーで検知する
プロキシはアクセスキーのセンチネルを見て、SigV4リクエストだと判別します。
- 3
実値に差し替えて再署名する
実値に差し替えたうえで、署名を計算し直してAWSへ送ります。
AWS_ACCESS_KEY_ID・AWS_SECRET_ACCESS_KEY・AWS_SESSION_TOKENという定番の名前で、値全体をマスクしていれば、Claude Codeは自動で1つの認証情報として結びつけます。変数名が違うときだけ、awsPairsで組にします。
{
"sandbox": {
"credentials": {
"awsPairs": [
{
"accessKeyIdVar": "MY_KEY_ID",
"secretAccessKeyVar": "MY_SECRET_KEY",
"sessionTokenVar": "MY_SESSION_TOKEN"
}
]
}
}
}awsPairsに書く変数は、extractやdecodeを使わず値全体をマスクするmaskエントリでなければなりません。sessionTokenVarは一時認証情報のときだけ書き、プロキシは再署名したリクエストにx-amz-security-tokenとして実値を載せます。
シークレットだけをマスクすると、プロキシが検知できず署名の壊れたリクエストがAWSへ届いて失敗します。この場合は起動時に警告が出ますが、アクセスキーIDだけをマスクした場合は警告が出ません。x-amz-dateヘッダーのない検知済みリクエストのように再署名できないものは、壊れた署名を送らずプロキシのエラーで失敗します。
再署名できないリクエスト形式も3つあります。
| リクエスト形式 | sigv4のキー | 再署名できない理由 |
|---|---|---|
| aws-chunkedのストリーミングアップロード | sigv4のキーstreaming | 再署名できない理由各チャンクの署名が先頭の署名から連鎖しており、本文の書き換えが必要になる |
| Presigned URL | sigv4のキーpresigned | 再署名できない理由署名がURL自体に埋め込まれ、Authorizationヘッダーを持たない |
| SigV4A(非対称署名) | sigv4のキーsigv4a | 再署名できない理由共有鍵によるHMACの再計算ができない |
センチネルで署名されたこれらのリクエストは、既定ではプロキシが失敗させます。credentials.sigv4で該当の形式をpassthroughにすると、センチネル由来の署名のまま転送され、呼び出し元のツールはプロキシのエラーではなくAWS自身の拒否レスポンスを受け取ります。実値で署名した(マスクしていない)リクエストには影響しません。
maskを書いたのに保護されない3つの場合
mask を設定しても実値が読める・届かない場合
パターンが空振りしたファイル
ファイルの
onExtractNoMatchの既定はwarnです。警告を出してそのエントリを飛ばすので、サンドボックス内のコマンドは実ファイルをそのまま読めます。秘密があるのにパターンが外れるかもしれないときはdenyにします。errorはサンドボックスの起動を止めます。分離をオフにしたときや、filesystem.allowReadのエントリが対象パスを開き直しているときは、読み取りを止められないのでdenyがerrorとして扱われます。JWTでなかった環境変数
環境変数の
decode: "jwt"は、値がJWTと検証できないかmaskClaimsに一致するクレームがないと、警告を出して変数を素通しします。extractが空振りしたときも、onExtractNoMatchの既定はwarnで素通しです。denyなら変数を外し、errorなら起動を止めます。denyにフォールバックしたmaskで分離をオフにした場合
ディレクトリやglobなどで
denyにフォールバックしたエントリは、ほかのファイルdenyと同じく、ファイルシステム分離をオフにすると効きません。maskと書いていても、この場合は実ファイルが読めます。補足すると、環境変数のdenyとmask、ファイルのmaskそのものは分離と独立に効き続けます。macOSのファイルmaskはdenyとして読み取りを止め、分離をオフにしても止まったままです。
ファイルにはmaskDuplicatesという項目もあります(既定はfalse)。extractやdecodeで見つけた値と同じ文字列が、一致した範囲の外にも残っている場合に、それも置き換えます。生の部分文字列で照合するので、短い値や一般的な値だと無関係な箇所まで置き換わります。長くてエントロピーの高い秘密だけに使う項目です。
手元で設定の効きを確かめる
--settingsで渡す設定は、リポジトリを汚さずに試せます。手元のClaude Codeで、次の出力を確認しました(v2.1.285)。
claude --version
claude --help | grep -A 2 -- "--settings"
claude doctor --help出力の要点は次のとおりです。
2.1.285 (Claude Code)
--settings <file-or-json> Path to a settings JSON file or a JSON
string to load additional settings from
Check the health of your Claude Code installation. Reads settings files in the
current directory without a trust prompt.claude doctorは、カレントディレクトリの設定ファイルを信頼確認なしで読みます。信頼していないリポジトリの中で実行しても、そのリポジトリの設定を読む点は覚えておく価値があります。
maskの効きは、サンドボックス化したコマンドに実際に読ませて確かめます。Claudeにcat ~/.config/gh/hosts.ymlをサンドボックス内で実行させると、Linux・WSL2ではトークンの位置にセンチネルが表示され、macOSでは読み取りが失敗します。
強制終了したセッションの後始末にも注意が要ります。LinuxとWSL2のサンドボックスは、実行中に0バイトの読み取り専用ファイルをプレースホルダーとして作り、終了時に消します。SIGKILLなどで消える前に止まると残り、以降のセッションで設定の書き込みが失敗します。claude doctorが残りを列挙するので、ほかのセッションが動いていないときにrmで消します。v2.1.257より前は、残っていても警告されませんでした。
機能が広がってきた経緯
sandbox.credentialsは、denyだけの状態から3回の更新で範囲が広がっています。導入前に手元のバージョンを確認しておくと、設定が効かない原因を切り分けられます。
sandbox.credentialsの拡張
- v2.1.187deny専用で新設
files・envVarsの
mode: "deny"で、読み取りと環境変数を遮断します。 - v2.1.199環境変数のmask
network.tlsTerminate(実験的)でプロキシが実値へ差し替えます。 - v2.1.221ファイルのmask
Linux・WSL2でセンチネルコピーを読ませ、
extractで部分マスクします。詳細はv2.1.221のリリースノートにあります。 - v2.1.224JWTとAWS対応
環境変数の
extract、decode: "jwt"、maskClaims、awsPairs、sigv4を追加します。詳細はv2.1.224のリリースノートにあります。
まとめ
通信するツールにはmask、触らせたくない鍵にはdenyと、対象ごとに書き分けます。書いたあとは、サンドボックス内でcatを実行させて、実値が隠れているかを一度は見ておくと安心です。
OSレベルのマスクでなく実行環境ごとホストから切り離したい場合は、DevContainerでの隔離という選択肢もあります。個人情報保護法の委託先監督義務の観点からの検証は認証情報マスキングは情報漏洩対策になるかにまとめました。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。