プロンプトに書かずにコーディングエージェントへシークレットを渡す方法
エージェントがコマンドを動かすのに Stripe のキーやデータベースのパスワードを必要としています。チャットに貼り付ければ、それはディスク上のトランスクリプトに入り、モデルプロバイダーに届き、ときにはコミットにまで残ります。私たち自身の三つの失敗のあとにたどり着いたルール。シークレットがエージェントに届く経路は環境変数だけで、ほかにはありません。
状況はいつも同じです。エージェントがタスクの途中まで進み、テスト用の Stripe API を呼ぶ、ステージングのデータベースにマイグレーションをかける、プライベートレジストリにプッシュする、といった必要が出てきます。エージェントがキーを求めます。キーはパスワードマネージャーの中にあり、エージェントは待っていて、世界でいちばん速い手段はそれをチャットに貼り付けることです。
その貼り付けが、キーがあなたの管理から出ていく瞬間です。モデルに悪意があるからではありません。プロンプトが「保存され、転送される」オブジェクトだからです。ディスク上のトランスクリプトファイルに入り、モデルプロバイダーに届き、会話を引き継ぐ再試行やサブエージェントのたびにコピーされます。
私たちはコーディングエージェントを動かすデスクトップアプリ AgentsRoom を作っています。だからこの問題は自分たちで解くしかありませんでした。2026 年 9 月に三回間違えました。この記事は、そこでたどり着いたルールと、それを生んだ三つの失敗、そして私たちのアプリを使うかどうかに関わらず適用する方法です。
短い答え
- シークレットがエージェントに届く経路は環境変数であり、それ以外にはありません。 メッセージでも、チケットでも、スキルでも、コマンドラインのオプションでもありません。
- エージェントが書くのは
$NAMEであって、値ではありません。 コマンドが走るときにシェルが展開します。 - 値を保存するのは一度だけ、リポジトリの外に。 OS のキーチェーンで暗号化し、ほかの場所ではすべて名前で参照します。
- 会話を一度通ったシークレットは焼けています。 ローテーションして、それから後片付けです。
- マスキングは安全網であって、計画ではありません。 キーらしく見えるものは捕まえますが、単語のように読めるデータベースのパスワードは捕まえません。
その貼り付けは見た目より悪い
三つのことが同時に起きますが、はっきり見えるのは最初のひとつだけです。
モデルプロバイダーに送られます。 これは誰もが気づく部分です。あなたのキーは今、他人のインフラ上のリクエストボディの中、しかもあなたが編集できない会話の中にあります。
ディスクに、平文で残ります。 Claude Code、Codex、Antigravity CLI、そのほかの CLI は、会話を再開できるようにトランスクリプトを残します。そのファイルはあなたのホームフォルダに、暗号化されないまま、CLI が履歴を保持しているあいだずっと置かれます。一度、自分のトランスクリプトを sk- で検索して、何が出てくるか見てみてください。
横に広がります。 会話はエージェントの記憶なので、会話をコピーするものはすべてキーをコピーします。再開したセッション、フォークしたエージェント、親のコンテキストごと起動したサブエージェント、プロジェクトのファイルに書かれた要約。キーがチャットのメッセージからチケットの説明へ、そこから二つ目のエージェントの最初のメッセージへ移っていくのを、私たちは実際に見ました。誰も悪いことはしていません。
そして、いつもの安全網は持ちません。出力のマスキングはパターンで働きます。sk-…、glpat-…、JWT の形。thursday-harbour-71 があなたのステージングのデータベースのパスワードだと知ることはできません。私たち自身のアプリでも、一致した値をターミナルの出力からマスクしていますが、それでもあれは空中ブランコの下に張ったネットであって、ブランコそのものではないと考えています。
扉はひとつ、というルール
ルールは一行です。認証情報がエージェントに届く経路は、エージェントが動いているプロセスの環境変数であり、ほかのどの経路でもありません。
それ以外はすべて、そのままの文字列です。あなたが打つメッセージ、チケットの説明、スキルのファイル、システムプロンプト、コマンドラインのオプション。これらはすべて人間かモデルが読むものなので、そこに書かれたシークレットは公開されたシークレットです。
コマンドラインには一文を割く価値があります。チャットのメッセージより安全に感じられるのに、実際はそうではないからです。--api-key=… に入れた値は、ps を通してマシン上のすべてのプロセスから見え、シェルの履歴に残り、起動されたコマンドを記録するものすべてにログされ、プロセスの行をそのまま表示し返す診断ツールにも現れます。ひとつのプロセスの環境変数を読めるのは、そのプロセスと root だけです。面は小さく、手軽さは同じです。
一方でエージェントは、値そのものを必要としたことがありません。必要なのは、こう書くことだけです。
curl -H "Authorization: Bearer $STRIPE_SECRET_KEY" https://api.stripe.com/v1/charges
シェルは実行時に $STRIPE_SECRET_KEY を展開します。エージェントが書いたのは 17 文字とドル記号。そのトランスクリプトに残るのも 17 文字とドル記号です。
私たちのアプリではどう動くか
仕組みそのものより形のほうが大事ですが、実装をひとつ見られると形は真似しやすくなります。
保管庫はひとつ、ファイルもひとつ。 AgentsRoom はシークレットを、あなたのホームフォルダにある 1 つのファイル、モード 0600 に保存し、オペレーティングシステムのキーチェーンで暗号化し、書き込みのたびにそれを確認し直します。サーバーに同期されるものはなく、別のコンピューターに渡るものもありません。シークレットは、それを持っているマシンのものです。キーチェーンが使えないときは、平文に落ちるのではなく保存が失敗します。
値を保存するのは一度だけ。 コマンドと接続 > シークレット、どのプロジェクトからでも開けます。名前(英字、数字、ドット、ハイフン、アンダースコア、64 文字まで。そのまま環境変数の名前として使えます)と、パスワード欄に打ち込む値。表示される行には名前とその参照 {{secret:STRIPE_SECRET_KEY}}、そして参照をコピーするボタンが並びます。「表示」ボタンも「値をコピー」ボタンもありません。値をインターフェースに返すコードの経路が、そもそも存在しないからです。
参照するのは、環境が組み立てられる場所です。 エージェントを編集 > 詳細 > 環境変数、あるいはプロジェクトの設定、プロバイダーの設定、開発用のコマンドラインです。
STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}
起動時、メインプロセスがこれから作るターミナルの環境に値を差し込みます。インターフェースは一度も見ず、エージェントも見ず、スマホも見ません。画面の流れは シークレットマネージャーのページ にあります。
エージェントが知るのは名前であって、値ではありません。 私たちの MCP ツールにつながったエージェントは secrets_list を呼び、そのマシンにある名前の一覧を受け取れます。役に立つにはそれで十分です。「STRIPE_SECRET_KEY が保存されていますね。このエージェントの環境に STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} を足してください。私は $STRIPE_SECRET_KEY を使います」と言えます。値を読むことはできません。その値を使うコマンドを実行している最中でも、です。
私たちがリリースしてしまった三つのミス
書いてしまえば、上のルールは当たり前に見えます。これは三つのバグが残した傷跡です。どれも 2026 年 9 月、どれもユーザーからの報告でした。
チケットに書いた参照が、解決された状態で出てきた
9 月 19 日。 Robert はチケットの説明に対応表を書いていました。チケットを引き受けた人が何を設定すればいいか分かるように、という至極まっとうな STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} です。API からチケットを読み直すと、参照はそのまま返ってきました。ところが、そのチケットから起動したエージェントは、最初のメッセージで値を受け取っていました。
原因は配管の細部でしたが、結果は本物でした。チケットのブリーフはエージェントの最初のプロンプトになり、それは起動コマンドの環境変数の中を通っていました。私たちのリゾルバは環境の値をすべて置き換えていたので、プロンプトも置き換えていたのです。キーはディスクに行き、モデルプロバイダーにも行きました。ローテーション以外に逃げ道はありません。
修正は、エージェントの起動コマンドをそのままの文字列として扱うことでした。環境にエージェントの ID が入っているときは、最初のプロンプトを持つ変数には手を触れません。同じ変数を使う開発用のコマンドにはエージェントの ID がないので、これまでどおり解決されます。副作用はわざと受け入れました。エージェントの CLI オプションに打ち込んだ参照も解決されなくなりますが、コマンドラインはどのみち ps とログに残るので、これで正しいのです。
教訓。扉はわざと狭くしておく必要があります。 「役に立ちそうなところは全部置き換える」は、シークレットが絶対にあってはならない場所にたどり着く道そのものです。
メッセージに貼った参照は、いつまでも解決されない
9 月 22 日。 Cs040618 は逆の問題を報告してきました。「参照を会話に貼り付けているのに、エージェントがこのキーが何なのか分かってくれない」。
バグではありません。メッセージは設計としてそのままの文字列で、それは三日前の修正そのものです。ただ、その操作をした瞬間にアプリは何も言いませんでしたし、メッセージの中で {{secret:…}} を受け取ったエージェントのほうも、何と答えればいいのか分からなかったのです。
修正は三つ、どれも扉の場所を伝えるためのものです。入力欄に参照を貼り付けたときに出て環境変数を指し示すヒント、シークレットの一覧の上に置いた説明文(参照が置き換えられる三つの欄の名前を挙げ、「メッセージの中では決して置き換えられない」と書いてあります)、そして参照がメッセージで届いたときに何と答えるかを書いたエージェント自身の指示の一行です。
「設定 > シークレット」は存在しなかった
9 月 28 日。 Mohammad のエージェントが報告を出しました。ユーザーは設定 > シークレットに OTPIQ_API_KEY を追加し、三十分のあいだに何度も保存したのに、secrets_list は相変わらず同じ三つの古いシークレットを返す、と。
保存の処理自体は問題ありませんでした。シークレットが保管庫に届いたことが一度もなかったのです。設定にシークレットのページは存在しないからです。デスクトップアプリの六つのヘルプ文、このサイトの一ページ、そしてエージェント自身の指示が、揃って存在しない画面へ人を送っていました。設定に本当にあるのは環境変数のブロックなので、彼はそこにトークンを、波括弧で囲んで打ち込みました。それは参照ではありません。置き換えられることはなく、そのままエージェントに送られ、しかもプロジェクトと同期されるので、トークンはマシンの外に出ていきました。
私たちは文言を直して本当の場所を書き、エージェント向けのツールにシークレットの追加場所を言わせ、環境のブロックに検査を足しました。平文に見える認証情報、参照ではない波括弧付きの名前、このマシンに存在しないシークレットへの参照を、それぞれ指摘します。
ここでの教訓はシークレットの話ですらありません。ヘルプ文の中の間違った場所は、セキュリティのバグです。 ユーザーを、いちばん近くにある「それらしい欄」へ動かしてしまうからです。
AgentsRoom を使っていない場合
ルールは持ち運べます。あなたの環境に合うものをどれか選んでください。どれも同じ場所、つまりエージェントが動くプロセスの環境変数にたどり着きます。
- パスワードマネージャーの CLI。 1Password なら
op run -- claude、Bitwarden や Doppler にも同等のものがあります。ツールがテンプレートから参照を読み、子プロセスの環境に本物の値を注入し、ディスクには何も書きません。 - gitignore した
.envrcとdirenv。 単純で、ローカルで、ディレクトリごと。最初の値を入れる前に、.envrcと.env*を.gitignoreに足してください。あとからではなく。 - エージェントが決して読まない
.envファイル。 リポジトリの外に置き、エージェントを起動するシェルで読み込みます。ファイルがリポジトリの中にあれば、エージェントはいつかそれを開き、メッセージに引用し、あなたは振り出しに戻ります。 - クラウドの認証情報は、短命のものを。 CI では長命の AWS キーではなく GitHub OIDC、サービスアカウントのキーファイルではなくワークロード ID を使います。
どれを選ぶにせよ、エージェントが聞いてこないように、このルールをエージェントの指示に書いてください。CLAUDE.md か AGENTS.md の一段落で足ります。認証情報の値を決して求めないこと、ファイルに決して書かないこと、$NAME を使い、どの変数を設定すればいいかをユーザーに伝えること。まさにそれだけを書いた小さなスキルを公開しています。五行しかありません。
キーがすでに会話を通ってしまったら
この順番で。そして最初の手順は省略できません。
- ローテーションする。 プロンプトは取り消せません。その値は漏れたものとみなして、新しいものを発行してください。
- トランスクリプトを探す。 CLI は会話のファイルをあなたのホームフォルダに残します。そこで古い値を検索し、一致したものを削除するか切り詰めます。
- テキストをコピーする場所を点検する。 クリップボードマネージャーの履歴、エージェントにブリーフを渡したチケットや issue、エージェントがリポジトリに書いた要約、そしてキーを持っているあいだにエージェントがコミットしていたなら
git log -p。 - 扉を立てるのは、そのあと。 新しい値を環境変数の後ろに置き、名前で参照し、次のエージェントが聞いてこないように指示を更新します。
手順 4 を最後にするのは、漏れたキーのまわりにきれいな仕組みを作っても、整理された漏洩にしかならないからです。
実際によく聞かれる質問
API キーを AI エージェントへのプロンプトに絶対に書いてはいけないのはなぜですか ?
プロンプトは保存され、転送されるからです。Claude Code、Codex、そのほかの CLI は会話をあなたのディスク上のトランスクリプトファイルに書き出し、メッセージ自体はモデルプロバイダーに届きます。そこから先、キーはクリップボードの履歴にも、メッセージをそのまま写すあらゆるログにも、会話をコピーする再試行やサブエージェントにも残ります。パターンによるマスキングが捕まえるのは sk- や glpat- のような形をしたものであって、普通の単語に見えるデータベースのパスワードではありません。
読めないシークレットを、コーディングエージェントはどうやって使うのですか ?
環境変数を通して使います。エージェントが動いているプロセスの環境変数に値を入れておけば、エージェントはコマンドに値ではなく $STRIPE_SECRET_KEY と書きます。コマンドが走るときにシェルがそれを展開します。エージェントは文字そのものを一度も見ないので、会話にもトランスクリプトにも入りません。
代わりにコマンドラインの引数でシークレットを渡してもいいですか ?
できますが、やめてください。コマンドラインは ps を通してマシン上のすべてのプロセスから見えますし、シェルの履歴にも、プロセスのログにも、起動されたコマンドを読み取る診断ツールにも入ります。ひとつのプロセスの環境変数を読めるのはそのプロセスと root だけで、こちらのほうが面は格段に小さくなります。
AgentsRoom はエージェントが使うシークレットをどこに保存していますか ?
ホームフォルダにある 1 つのファイル、モード 0600、オペレーティングシステムのキーチェーンで暗号化され、そのマシンの上だけに置かれます。サーバーに同期されるものはなく、値をもう一度表示するボタンもありません。アプリが見せるのは名前、エージェントが見るのも名前で、値が復号されるのはターミナルやコマンドが起動する瞬間のメインプロセスの中だけです。
シークレットの参照をエージェントへのメッセージに貼り付けたらどうなりますか ?
何も置き換えられません。これは意図した仕様です。メッセージ、チケットの説明、スキル、システムプロンプトはそのままの文字列なので、そこに書かれた参照はテキストとしてエージェントに届きます。置き換えられるのは環境変数の値だけです。私たちはこれを痛い目で学びました。以前は、チケットの説明に書いた参照が、そこから起動したエージェントの最初のメッセージでは解決された状態になっていたのです。
キーがすでに会話を通ってしまいました。どうすればいいですか ?
ほかの何よりも先に、まずキーをローテーションしてください。送ったものは取り消せないからです。それはプロバイダー側にもあり、あなたのディスク上のトランスクリプトファイルにもあります。そのあとで、キーを含むトランスクリプトを削除するか切り詰め、クリップボードマネージャーを確認し、エージェントがファイルに書いていないかリポジトリを grep します。新しい値を環境変数の後ろに置くのは、そのあとです。
まとめ
エージェントはあなたのキーを読む必要がありません。必要なのは、環境にすでにそれが入っているプロセスです。
値を保存するのは一度だけ、リポジトリの外に、OS に暗号化させて。環境が組み立てられる場所で名前で参照する。エージェントには $NAME と書かせる。ほかの経路はすべてそのままの文字列のままにしておく。置き換えがあれば便利だったはずの経路も含めてです。便利な経路とは、まさに保存され転送される経路のことだからです。
そして間違えたときは、必ず間違えるものですが、先にキーをローテーションし、片付けはそのあとです。
AgentsRoomをダウンロード
すべてのAIエージェントを、すべてのプロジェクトで、ひとつのウィンドウから実行。
コンパニオンアプリ:外出先でもエージェントを確認
Claude、Codex、Antigravity CLI、またはその他の AI プロバイダーを使用します。
バグや要望を公開バックログに直接送信できます。
続きを読む
Codex、OpenCode、Antigravity、Claude Code の音声モード:どの CLI にあるのか?
2026年10月13日に、5 つのコーディングエージェント CLI と 2 つのアプリを確認しました。Codex CLI と oh-my-pi は音声で会話でき、Claude Code と Antigravity CLI はプロンプトへのディクテーションだけ、OpenCode には何も組み込まれていません。それぞれの起動方法、音声がどこへ送られるか、そして音声がトークンでいくらかかるかを解説します。
記事を読むClaude Code の Routines:クラウドで動くもの、自分のマシンに残るもの、そして選び方
Routines は、保存したプロンプトをあなたなしで実行するための Claude Code の仕組みです。スケジュール、API 呼び出し、GitHub のイベントをきっかけに、リポジトリを新しくクローンした cloud session として動きます。現在はリサーチプレビューの段階です。Claude Code には作業をローカルでスケジュールする方法も 2 つあり(デスクトップアプリのスケジュールタスクと /loop)、3 つの挙動は同じではありません。最小間隔、ローカルファイルへのアクセス、権限確認、ノートパソコンがスリープしたときに何が起きるか。このガイドではドキュメントに書かれた制限とともに 3 つを並べ、そのうえで私たち自身の 7 つの夜間エージェントがなぜローカルのマシンで動いているのかを説明します。
記事を読むClaudeアカウント1つを2台のパソコンで、または2人で使う:どこまでできるか
1つのClaudeアカウントは手持ちのすべてのデバイスでログインでき、どのデバイスも同じ5時間の上限と週次の上限を、ClaudeとClaude Codeの合計で使います。2人で1つのアカウントを使うのはAnthropicの規約違反で、ファミリープランもありません。2026年10月25日に確認した内容と、どのパソコンが何を使ったかを見る方法をまとめました。
記事を読む