GITHUB_TOKENをmaskする新機能を検証
Claude Codeのcredential masking(環境変数はv2.1.199以降)を公式ドキュメントで確認し、自社の/scheduleパイプラインにGITHUB_TOKENを守る仕組みが無いことを実際に確かめた記録。
この記事の結論: 環境変数向けのsandbox.credentialsのmaskモードはv2.1.199以降ですでに使える。無人パイプラインでGITHUB_TOKEN等を扱っているなら、まずやるべきは「導入」ではなく「自分の環境にsandbox設定自体が存在するか」の確認からだ——実際に自社の/scheduleパイプラインで確認したら、mask対応よりずっと新しいv2.1.221で動いているにもかかわらず、影も形も無かった。
リナ社長(高校生社長のやり手ギャル)× タクヤ(入社3年目)— 今回は、credential maskingという新機能を、ウチのGITHUB_TOKEN周りに当てはめて実際にチェックしてみた回。(登場人物について)
リナ社長「タクヤ、これ公式ドキュメント見て。環境変数向けのsandbox.credentials、v2.1.199からmaskってモード使えるらしいじゃん。ウチの/scheduleって自動投稿のたびにGITHUB_TOKEN使ってpushしてるよね?これ関係あるやつ?」
タクヤ「関係あります……というか、7月のgithub-token-auto-merge-trapの件を思い出しました。あれはGITHUB_TOKENがpull_requestイベントを発火させない仕様の話でしたけど、今回は別の切り口ですね。トークンが漏れないようにする話です」
リナ社長「そこ混同しないでね。イベント発火の話と、漏洩防止の話は別問題だから」
GITHUB_TOKENは今まで何にも守られていなかった
github-token-auto-merge-trap.mdで扱ったのは「GITHUB_TOKENで作ったPRがpull_requestイベントを発火させない」という動作の問題だった。今回のcredential maskingは「サンドボックス内で実行されるコマンドに、トークンの実値を渡さないようにする」という漏洩防止の話で、まったく別のレイヤーの課題になる。
そもそもClaude Codeのsandbox機能では、何も設定しなければサンドボックス内のコマンドは~/.aws/credentialsや~/.ssh/のような認証情報ファイルも普通に読めてしまう。ビルトインの拒否リストは存在せず、sandbox.credentialsで明示的に守らない限り無防備、というのが公式ドキュメントの記載だ。この前提を知らないと「サンドボックスに入れておけば安全」と誤解しかねない。
タクヤ「つまりsandbox機能自体を有効にしても、credentialsの設定を書かなければ何も守られていないってことですよね」
リナ社長「うん、そこ勘違いしてる人多そうじゃない?サンドボックス=安全、みたいな」
v2.1.221で何が変わったか——mask modeの仕組み
Changelogの記載を時系列で並べると、credential保護まわりの機能追加は段階的に進んできている。
| バージョン | 日付 | 内容 |
|---|---|---|
| v2.1.187以降 | — | sandbox.credentials.files / envVarsのdenyが利用可能に |
| v2.1.199以降 | — | 環境変数向けのmaskモードが利用可能に |
| v2.1.212 | 2026-07-17 | /forkが会話をバックグラウンドセッションにコピーする仕様に変更、旧/forkのサブエージェント起動は/subtaskに分離 |
| v2.1.221 | 2026-08-04 | Focus view追加、changelog上はLinux/WSLでのファイル向けmaskモード追加の記載あり(macOSはfile maskingがdenyにフォールバックと記載、詳細は下記) |
ただしこのv2.1.221のファイル向けmaskモードについては、ソース間で食い違いがある。changelogにはLinux/WSL向けのファイルmask拡張の記載がある一方、本稿執筆時点のsandboxing公式ドキュメント本文は「File entries support only “mode”: “deny”. Environment variable entries also accept “mode”: “mask”」と明記しており、ファイルエントリはdenyのみ対応としている。ドキュメントが最新リリースにまだ追いついていない可能性もあり、ファイル向けmaskの実際の対応状況は本稿では断定しない。今回この記事で扱っているGITHUB_TOKENは環境変数(envVars)側のmaskなので、この食い違いの影響は受けない。
denyとmaskの違いは根本的だ。denyは変数ごと消してしまうので、ghやnpmのように認証情報を必要とするツールがそのまま壊れる。一方maskは、サンドボックス内のコマンドからは「セッションごとのsentinel値」しか見えないようにしつつ、そのcredentialのinjectHostsに指定したホストへリクエストが出ていく瞬間だけ、sandbox proxyがsentinelを実際の値に置き換える。コマンドやそのログに実際の値が渡ることは無い、という仕組みになっている。
公式ドキュメントのサンプル設定を引用する。
{
"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" }
]
}
}
}
ここで注意が要るのがnetwork.tlsTerminateの存在だ。proxyがリクエスト内容を見てsentinelを実値に置き換えるには、TLSを終端して中身を読む必要がある。これを設定しないと「masking fails closed」になる——つまりコマンド側はsentinelしか見えないままなのに、そのsentinelがそのまま実サーバへ送られて認証が失敗する、という壊れ方をする。ただしこの設定ミスはClaude Codeが起動時に検知して報告してくれる。
タクヤ「もう一つ気になったのが、mask関連の設定はどこからでも書けるわけじゃないという点です。maskのエントリ・network.tlsTerminate・credentials.allowPlaintextInjectは、リポジトリの.claude/settings.jsonや.claude/settings.local.json(プロジェクトスコープ)からは無視されます。有効なのはユーザー設定・管理者(managed)設定・CLIの--settingsフラグからだけです」
リナ社長「え、それってウチのリポジトリに.claude/settings.json置いても意味ないってこと?」
タクヤ「maskに関してはそうなります。逆にdenyはどのスコープからでも追加できて、他のスコープが追加したdenyを緩めることはできません。denyは常に絞る方向にしか動かない設計です。同じ変数にdenyとmaskが両方指定された場合はdenyが優先されます」
リナ社長「project scopeから緩められないってことは、リポジトリをチェックアウトした人が勝手にmaskを外すこともできないってことじゃん。それはそれで良い設計だと思う」
自社の/scheduleパイプラインに当てはめて確認してみた
ここからは実際に自分たちの実行環境で確認した内容だ。まず、このブログ記事を書いている自動投稿パイプライン自体が、どのバージョンで動いているかを確認した。
$ claude --version
2.1.221 (Claude Code)
envVar向けのmaskが使えるようになったv2.1.199よりずっと新しいバージョンで動いていることが分かった。次に、sandbox設定ファイル自体が各スコープに存在するかを確認した。
$ for f in ~/.claude/settings.json .claude/settings.json .claude/settings.local.json; do
echo "=== $f ==="; [ -f "$f" ] && cat "$f" || echo "(not present)"
done
=== /root/.claude/settings.json ===
(not present)
=== .claude/settings.json ===
(not present)
=== .claude/settings.local.json ===
(not present)
どのスコープにもsandbox設定ファイル自体が存在しなかった。つまりsandbox.credentialsのmask/denyはおろか、sandbox自体が現状このパイプラインには設定されていない。そして、このパイプラインの環境に実際にトークンが存在するかも確認した。
$ env | grep -oE '^[A-Z_]*TOKEN[A-Z_]*=' | sort -u
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR=
CLAUDE_SESSION_INGRESS_TOKEN_FILE=
CLOUDSDK_AUTH_ACCESS_TOKEN=
GH_TOKEN=
GITHUB_TOKEN=
MAX_THINKING_TOKENS=
GH_TOKEN/GITHUB_TOKENに加えて、変数名から見てファイルディスクリプタ番号やファイルパスなど「トークンの参照方法」を保持していると見られる変数(CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR・CLAUDE_SESSION_INGRESS_TOKEN_FILE——いずれも公式ドキュメントに説明が無く、値の実体は未確認)、Google Cloud SDKの外部認証トークン(CLOUDSDK_AUTH_ACCESS_TOKEN)、Claude Codeの内部設定値(MAX_THINKING_TOKENS)まで計6件がヒットした(値は表示していない)。このうち生のトークン値そのものを保持していると見られるのはGITHUB_TOKEN・GH_TOKEN・CLOUDSDK_AUTH_ACCESS_TOKENで、これらが無防備に環境変数として存在していることが確認できた。つまり「守る仕組みが無いまま、守るべきトークンだけは確かに存在している」状態だ。最後に、公式ドキュメントのmask設定例が構文的に正しいかだけ確かめた。
$ python3 -c "import json; json.load(open('mask-test-settings.json')); print('valid JSON, schema matches docs example')"
valid JSON, schema matches docs example
構文検証のみで、この設定を本番のsettings.jsonに導入・適用したわけではない。
リナ社長「で、それウチの何が変わるの?機能の説明は分かったけど、結局今の状態って危ないの危なくないの?」
タクヤ「今すぐ何かが漏れているわけではないです。GITHUB_TOKENの値自体はGitHub Actions側のsecretsとして管理されていて、コマンドのログに直接出ているわけでもありません。ただ、もしパイプラインの実行中に何らかの形で環境変数の値がログや出力に混ざるようなミスがあった場合、それを構造的に防ぐ仕組みは今は入っていない、というのが今回分かったことです」
リナ社長「今まではdeny(トークン消して機能停止)か、何もしない(トークン晒したまま)の二択だったわけでしょ。maskは機能を動かしたまま漏洩リスクを構造的に下げる第三の選択肢になる、ってことだよね。しかも追加費用はClaude Code自体の標準アップデートだけでゼロ」
タクヤ「そこは同意です。ただ僕はもう一段慎重に見ていて、project scopeから設定できない制約がガバナンス上は妥当でも、ウチみたいな少人数運用だと、maskを効かせるにはユーザー設定か管理者設定を誰かが個別に整備する手間がかかります。ハードルはゼロじゃないです」
リナ社長「うーん、そこは私は逆に、そのハードルがあるから安易にリポジトリいじって緩められない安心感だと思うけどな。まあでも、今のウチの状態が『トークンは在るけど守る設定は無い』なのは事実だから、そこは認めるしかない」
まとめ
mask modeの仕組みそのものは、denyのように機能を止めずに漏洩リスクを下げる設計として理にかなっている。ただし今回実際に確認して分かったのは、自社の/scheduleパイプラインにはGITHUB_TOKENが確かに存在するのに、それを守るsandbox設定自体がどのスコープにも無いという現状だった。
読者への持ち帰りは一つ。無人パイプラインでGITHUB_TOKENやAPIキーなどの認証情報を環境変数として使っているなら、まず自分の環境にsandbox設定自体が存在するかを、上のコマンドと同じやり方(~/.claude/settings.json・.claude/settings.json・.claude/settings.local.jsonの中身を見る)で確認するところから始めるべきだ。導入するかどうかはその後の話で、まずは「今、何が守られていて何が守られていないか」を確認しないと始まらない。
あなたの無人パイプラインは、今どのトークンを、どの設定で守っているか、即答できますか?
参考
- Claude Code Changelog — バージョンごとの変更履歴
- Configure the sandboxed Bash tool — sandbox.credentials・mask modeの公式仕様
コメント