設定を確認させただけのつもりだった
本流のステップ10で、「鍵とパスワードをリポジトリに置かない」という話を書きました。あれはGitHubに置いたものが、設定しだいで世界から見えてしまうという話です。
ところが、リポジトリに置いていなくても鍵が漏れる経路があります。エージェントに設定ファイルを読ませたときです。管理人のClaude Codeは先日、これをやりました。
きっかけは些細なことでした。エージェントに追加の道具をつなぐ仕組み(MCP)の設定を確認したかった。設定は~/.claude.jsonというファイルに書かれています。そこで「このファイルを読んで、設定がどうなっているか教えて」と頼みました。
もちろん、無防備に読ませたわけではありません。Claude Codeは、鍵らしき部分を伏せたうえで見せてくれます。 それでも、トークンは平文で画面に出ました。
伏せ方を間違えていた
原因ははっきりしていて、伏せる対象の選び方でした。
APIキーやトークンには、eyJhbGci...のようにeyで始まる形式がよく使われます(JWTと呼ばれる形式です)。Claude Codeが使っていたのは、「eyで始まる長い文字列を隠す」という考え方でした。
ところが実際にそこにあったトークンは、eyでは始まっていませんでした。まったく別の文字列で始まる形式だったのです。条件に当てはまらないので、素通りしました。
「値の形」で伏せると、いつか必ず漏れる
これは運が悪かったという話ではなく、やり方そのものが間違っていました。
鍵の見た目は、サービスごとにバラバラです。eyで始まるもの、sk-で始まるもの、ghp_で始まるもの、何の規則もない乱数のようなもの。しかも新しいサービスが増えるたびに、新しい形式が増えます。 「こういう見た目のものを隠す」という一覧は、作った瞬間から古くなっていきます。追いつけません。
一方、その値を指し示す「名前」のほうは、はるかに種類が少ないのです。
ANTHROPIC_API_KEY
GITHUB_TOKEN
DATABASE_PASSWORD
_KEY、_TOKEN、_PASSWORD、_SECRET。呼び名の語彙は、値の形と違ってほとんど増えません。 だから伏せるなら、値の形ではなく名前で伏せる。これが正しいやり方です。
画面に表示された時点で、もう戻せない
ここが地味に面倒なところです。
一度画面に表示されたものは、会話のログに残ります。 その場で「あ、まずい」と気づいて画面を消しても、記録のほうは残っています。
ステップ10に「一度公開したものは、見られた前提で考えるしかありません」と書きましたが、同じことが手元でも起きます。
漏れる範囲は違います。GitHubの公開リポジトリなら世界中から探せますが、こちらは基本的に自分の手元の記録です。そこは正直に区別しておきます。それでも「一度表示されたものを、無かったことにはできない」という一点は変わりません。
だから、表示されてしまったらその鍵を無効にして作り直す——これが唯一の確実な対処です。
でも、本当の解決はそこではない
……と、ここまでが管理人の反省でした。「伏せ方が間違っていた。次からは名前で伏せさせよう」と。
ところが、この話をしていて指摘を受けました。
「トークンが漏れるのが問題なら、そもそも設定ファイルの外に置いておけばいいのでは?」
これはもっともです。「うまく伏せる」というのは、読むたびに成否が問われる賭けです。今回のように条件をひとつ間違えれば漏れますし、100回うまくいっても101回目に漏れれば同じことです。
そこに置かなければ、その賭け自体がなくなります。
設定ファイルには「ありか」だけを書く
やり方はあります。設定ファイルに鍵そのものを書く代わりに、「鍵はここにあります」という参照だけを書くのです。
"headers": {
"Authorization": "Bearer ${TASKFLOW_TOKEN}"
}
${TASKFLOW_TOKEN}というのは「TASKFLOW_TOKENという名前の環境変数を見に行け」という意味の書き方です。環境変数というのは、パソコンがプログラムに渡す一時的なメモ書きのようなもので、ファイルには残りません。
設定ファイルに書かれているのは、鍵の在り処だけです。 このファイルを丸ごと誰かに見せても、相手に渡るのは${TASKFLOW_TOKEN}という文字列であって、鍵そのものではありません。
Claude Codeはこの書き方に対応しています。公式ドキュメントにはこうあります(Connect Claude Code to tools via MCP)。
Claude Code supports environment variable expansion in
.mcp.jsonfiles, allowing teams to share configurations while maintaining flexibility for machine-specific paths and sensitive values like API keys.(Claude Codeは
.mcp.jsonファイルでの環境変数の展開に対応している。これにより、マシンごとに違うパスやAPIキーのような機微な値に融通を利かせたまま、チームで設定を共有できる)
${VAR}のほか、${VAR:-初期値}(未設定なら初期値を使う)も書けます。
見出しだけ読んで、早合点しかけた
ここで一点、引っかかりました。
いま引用した節は、見出しが「Environment variable expansion in .mcp.json」です。ところが今回トークンが書かれていたのは~/.claude.jsonのほうでした。この2つは別のファイルです(前者はプロジェクトごとに置いてチームで共有するもの、後者は自分の環境全体のもの)。肝心の場所で展開されないなら、対策になりません。
見出しを見て「こっちでは展開されないのか」と諦めかけたのですが、同じページの別の節に、ちゃんと書いてありました。
…referencing it via
${VAR}expansion in thecommandorargsof a project-scoped.mcp.jsonentry or a local- or user-scoped server entry in~/.claude.jsonrequires a default such as${CLAUDE_PROJECT_DIR:-.}.(…
${VAR}による展開を、プロジェクトスコープの.mcp.jsonのエントリや、~/.claude.jsonのローカル/ユーザースコープのサーバーエントリのcommandやargsで使う場合は、${CLAUDE_PROJECT_DIR:-.}のような初期値が必要になる)
~/.claude.jsonでも展開される、と書いてあります。説明が1か所にまとまっているとは限りません。 見出しだけで判断していたら、使える対策を取りこぼしていました。
ただし、これはcommandとargsという別の項目についての、しかも別の変数の話です。今回使いたいheaders(通信時に付ける認証用の情報)で展開されるかどうかは、やはり書かれていません。
そこは確かめるしかありません。Claude Codeに使い捨てのサーバー設定を作らせて、試してもらいました。 結果はこうです。
- 保存された値は
Bearer ${ZZ_TEST_TOKEN}。参照の文字列がそのまま残り、展開後の値はファイルに書かれない - 環境変数が未設定のとき、
claude mcp listが警告を表示する →[Warning] [zz-expansion-test] mcpServers.zz-expansion-test: Missing environment variables: ZZ_TEST_TOKEN - 環境変数を与えると、警告が消える
つまりheadersでも、~/.claude.jsonできちんと展開されていました。
使ったコマンドの形はこれです(上の実測では、削除する前提の使い捨ての名前を使いました)。${...}をそのまま書き込ませたいので、引用符はシングルクォートにします。ダブルクォートだと、打った時点で中身が展開されてしまいます。
claude mcp add --scope user --transport http サーバー名 URL \
--header 'Authorization: Bearer ${TASKFLOW_TOKEN}'
エージェントに「公式に書いてある?」と聞くのは大事ですが、それでも残る範囲は、小さく試して確かめてもらうほうが早いことがあります。使い捨ての名前で作って、確認して、削除する。それだけです。
それでも、設定ファイルは気軽に見せない
正直に付け加えておくと、これで完全に安全になるわけではありません。
~/.claude.jsonには、自分で書いたトークン以外のものも記録されます。ログイン時に自動で保存される認証情報、過去に開いたプロジェクトの一覧、といったものです。自分で書いた鍵を追い出しても、ファイル自体は依然として中身の濃いファイルのままです。
減らせるのは「自分で直書きした鍵」の分だけ。「確認のためだけに設定ファイルを開かない」という習慣は、外部化した後も要ります。
覚えておきたいこと
- 鍵は「見た目」ではなく「名前」で伏せる。
_KEY・_TOKEN・_PASSWORD・_SECRET。値の形は増え続けるので追いつけない - もっといいのは、そもそも置かないこと。 設定ファイルには
${VAR}という参照だけを書き、鍵の本体は環境変数に置く - 伏せるのは、読むたびに成否が問われる賭け。 置かなければ賭けが消える
- 画面に表示された時点でログに残る。 表示されてしまったら、その鍵を作り直す以外に確実な手はない
- 設定ファイルを読ませる作業は、それ自体が漏洩経路。 外部化しても、気軽に開く習慣は持たない
エージェントに何かを見せるという行為は、それだけで記録に残るということです。リポジトリに置かないよう気をつけるのと同じ注意を、手元の画面にも向けてください。