Dance with Agents

伏せたはずのAPIキーが、そのまま画面に表示された話

設定を確認させただけのつもりだった

本流のステップ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.json files, 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 the command or args of a project-scoped .mcp.json entry or a local- or user-scoped server entry in ~/.claude.json requires 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}という参照だけを書き、鍵の本体は環境変数に置く
  • 伏せるのは、読むたびに成否が問われる賭け。 置かなければ賭けが消える
  • 画面に表示された時点でログに残る。 表示されてしまったら、その鍵を作り直す以外に確実な手はない
  • 設定ファイルを読ませる作業は、それ自体が漏洩経路。 外部化しても、気軽に開く習慣は持たない

エージェントに何かを見せるという行為は、それだけで記録に残るということです。リポジトリに置かないよう気をつけるのと同じ注意を、手元の画面にも向けてください。

セキュリティの記事

セキュリティのページへ →