SSD Nodes Learn メモリ 8GB — 年額 $66
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-01

AIエージェントにAPI keyを渡さない方法

AIエージェントは1回のtool callでAPI keyを漏えいさせられます。実際のkeyではなく、credential gatewayの背後でスコープ付き短命tokenを発行する方法を解説します。

AIエージェントから秘密情報を除外する意味

AIエージェントは、コマンドを実行する通常の Linux プロセスです。そのプロセスが保持するすべての環境変数は、プロセスが実行するコードから読み取れます。そのため、エージェントの環境変数に API key があると、エージェントは到達可能な任意のホストにその key を送信できます。エージェントから秘密情報を除外するとは、key ではなく、短時間だけ有効なスコープ付き token、またはネットワーク境界で別の仕組みが実際の値に置き換える placeholder を渡すことです。

これは、モデルが敵対的になるという話ではありません。仕組みはもっと単純です。エージェントは、指示を含む web page、README、または issue comment を読み、その指示に従います。language model にとって、ユーザーが書いた text と取得した text に違いはないためです。これが prompt injection です。いったん発生すると、被害の範囲を決めるものは、プロセスが読み取れる内容だけです。まだ境界を設定していない場合は、server 上で coding agent を安全に実行する方法で、このガイドが前提とする隔離レベルを確認できます。

脅威モデルを平易に説明すると

エージェントが実行されるユーザーとして、これを実行します。

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

このコマンドが出力する各行は、第三者のサーバーへのHTTPリクエスト1回で外部に送信できます。次に、エージェントの近くのディスク上に何があるかを確認します。

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

シェルを持つエージェントがデータを外部に持ち出すために、巧妙なエクスプロイトは必要ありません。通常の4つの経路で実行でき、いずれもログ上は通常の処理に見えます。

  • 任意のホストへの送信curlまたはfetchで、値をクエリ文字列に含める。
  • エージェントが書き込み可能なリポジトリへのgit commitおよびgit push
  • パッケージのインストールスクリプト。エージェントのユーザーとして任意のコードを実行します。
  • 値を含むホスト名のDNSルックアップ。HTTPの送信トラフィックをブロックしていても外部に送信されます。

レビューだけでは解決できません。アクセス可能な場所に価値のあるデータを置かないことが対策です。

作業ツリー内の秘密情報はコンテキストウィンドウ内の秘密情報です

エージェントはファイルを読み取ります。エージェントが作業しているリポジトリ内の .env ファイルは読み取られ、読み取られた時点でコンテキストウィンドウに入ります。つまり、その内容はトランスクリプト、保存するログ、そしてエージェントが次に生成する内容に含まれます。

エージェントが作業するツリー内にキーがある場合:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

ファイルをエージェントの到達範囲外に移動した場合:

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

作業ツリーにファイルがないため、エージェントのユーザーはそのファイルを開けなくなります。エージェント自身の設定にある拒否ルールは第2の防御層であり、第1の防御層ではありません。Claude Code はプロジェクト内の .claude/settings.json から権限ルールを読み取ります:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

これにより、エージェントが調査中に誤ってファイルを開くことを防げます。ただし、注入された命令が base64 .env を実行することは防げません。これはファイルの読み取りではなく、shell コマンドだからです。設定はガードレールとして扱い、ファイルシステム権限を壁として扱ってください。同じ分離はコンテナ内にも当てはまります。Docker Compose の環境ファイルと秘密情報では、この問題を1層下で扱います。

各エージェントに専用の非特権ユーザーを割り当てる

エージェントを自分のユーザーとして実行すると、SSH keys、cloud credentials、shell historyを引き継ぎます。専用ユーザーの作成に必要なのは1つのコマンドだけで、これらすべてへのアクセスをなくせます。

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

最後の行はcat: /home/you/.ssh/id_ed25519: Permission deniedで失敗しなければなりません。代わりにkeyが表示される場合は、home directoryがgroupまたはworldから読み取り可能です。chmod 700 ~で修正できます。エージェントユーザーをsudoに追加しないでください。また、実際に必要な1つのコマンドより広いNOPASSWDルールを与えないでください。VPSでの最小権限ユーザーでは、groupとsudoersの詳細を説明しています。

cloud VPSでは、もう1つ境界を追加すると安全です。instance metadata serviceは固定されたlink local addressで応答し、要求したすべての対象にrole credentialsを渡すことがよくあります。

sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

エージェント側から確認します。sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/は何も出力せず、non zeroで終了するはずです。パケットはboxの外へ出る前に拒否されるためです。

境界で認証情報を注入する

この問題を実際に解決するパターンは、認証情報の注入です。エージェントが実際の暗号鍵を保持することはありません。エージェントはローカルゲートウェイ経由でリクエストを送信し、ゲートウェイが送信時にプレースホルダーを実際のシークレットへ置き換えます。シークレットはゲートウェイのストレージに保存され、別のプロセスが管理します。そのプロセスは別のユーザーとして実行されます。

OneCLIは、この方式を実装したオープンソースソフトウェアの1つです。ライセンスはApache-2.0で、エージェントの隣でコンテナとして実行します。2026年7月時点で、プロジェクトは次の構成を文書化しています。

git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --wait

ダッシュボードはポート10254で、ゲートウェイは10255でリッスンします。実際の認証情報を1回保存し、各エージェントには鍵の代わりにプレースホルダー値と、エージェント専用のスコープ付きアクセストークンを渡します。エージェントはこのトークンをProxy-Authorizationヘッダーで送信します。ゲートウェイはホストとパスに基づいて送信リクエストに対応する認証情報を特定し、一致する認証情報を復号して置き換えます。エージェントの環境には、盗まれる価値のある情報が残りません。

ここで重要なのは暗号化ではありません。「このエージェントは何を、いつ使用したか」という問いをログクエリで確認できる点です。6つの環境のどれに鍵のコピーがあったかを推測する代わりに、1つの監査証跡を確認できます。

環境ではなくプロセスに秘密情報を渡す

systemd でエージェントを実行する場合、環境変数は必要ありません。LoadCredential= は、同じサービスだけが読み取れる非公開ディレクトリに秘密情報を配置します。ユニットファイルでは %d として、プロセス内では $CREDENTIALS_DIRECTORY として公開されます。値が /proc/<pid>/environ に現れることはないため、ps eww で表示することもできません。サービスが停止すると、ディレクトリも消去されます。

最初に、認証情報をマシン向けに暗号化します。次のコマンドは systemd のドキュメントに記載されており、Ubuntu 24.04 と Debian 13 を含む systemd 250 以降で動作します。

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

最後のコマンドは sk-example-value を出力します。これにより、暗号化されたファイルをこのホストで復号できることを確認できます。次に、ユニットからそのファイルを参照します。

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

エージェントのコードは、値が必要になったときに $AGENT_KEY_FILE のファイルを開きます。ファイルの読み取りは一時的な処理です。一方、環境変数はプロセスの存続中、プロセスが起動するすべての子プロセスに残ります。

有効期間の短いトークンを、長期間有効なキーより優先します

有効期限のないキーは、数か月後にログや記録に現れても、引き続き有効です。サービスでセッショントークンを使用できる場合は、セッショントークンを使用し、作業に必要な最短の有効期間を設定します。

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

15 分が AWS STS(security token service)が受け付ける最短期間で、通常は1つのエージェントタスクに十分です。GitHubでは、エージェントユーザー専用の gh ログインを作成し、作業対象の単一リポジトリだけを対象にした fine-grained token を付与します。これにより、そのセッション内の gh auth token は、他のリソースにアクセスできない値を返します。まずリソースで範囲を限定し、次に時間で限定します。

検証し、その後も継続して確認する

エージェントの設定を変更した後は、3つのチェックを実行する価値があります。これらは自分自身ではなく、エージェントのユーザーとして実行してください。

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

1つ目は何も出力しないはずです。2つ目は ls: cannot open directory '/home/you/': Permission denied を出力するはずです。3つ目は、エージェントのネットワーク経路がどの認証情報を提示しているかを示します。これは、ゲートウェイパターンが答えるために存在する問いです。401 なら、エージェント自身の GitHub 認証情報は使われていません。200 なら、1つ使われているため、どのトークンか把握しておく必要があります。エージェントを無人で実行する場合は、VPSでAIエージェントのコストを管理する方法で、これらのアクセス制限と組み合わせる予算上限を確認できます。

FAQ

モデルが鍵を漏えいしないと、そのまま信頼できますか?

いいえ。この脅威モデルでは、攻撃者はモデルではないためです。agent は Web ページ、リポジトリ、issue tracker からテキストを読み込みます。そこには命令が含まれている可能性があります。モデルには、自分への命令と取得したテキストを確実に区別する方法がありません。モデルが正しく選択することに依存する制御は、注入された命令に説得力があると最初に破綻します。そのため、制御はオペレーティングシステムまたはネットワーク側に置く必要があります。

agent のシークレットに環境変数を使うのは、本当にそれほど危険ですか?

特定の点で危険です。環境変数は継承されるためです。agent が起動するすべての子プロセスが、そのコピーを取得します。これには build script、test runner、package install hook が含まれます。また、同じユーザーであれば /proc/<pid>/environ から環境変数を読み取れます。そのため、agent が値を渡さなくても、agent が実行する処理は環境変数を読み取れます。使用時点でファイルを読み込み、LoadCredential= または gateway を使用すれば、公開される期間をその時点に限定できます。

シークレットを vault に保存すれば、この問題は単独で解決しますか?

部分的にしか解決しません。vault は保存方法を改善します。しかし、vault からシークレットを取り出し、環境変数として agent に渡す最後の手順は解決しません。その場合、元の状態に戻ります。重要なのは、置換を実行する主体です。agent がシークレットを取得するなら、agent がシークレットを保持します。gateway または init system が agent のプロセス外で置換を実行するなら、agent がシークレットを保持することはありません。

agent がすでに何かを漏えいしたかどうかは、どう確認できますか?

通常、事後に確認することはできません。これが gateway を使用する理由です。gateway がなければ、証拠は shell history、agent の transcript、そしておそらく保存していない outbound connection log に分散します。credential gateway を使用すると、credential の使用ごとに、agent identity と timestamp を含む1行の記録が残ります。漏えいが疑われる場合は、最初に key を rotate し、その後で調査してください。rotation は安価ですが、確実な確認は容易ではありません。

今日、最低限何をすべきですか?

すべての .env file を agent が作業するディレクトリの外へ移動し、agent ごとに1つの unprivileged user を作成してください。この2つの変更は約10分で完了します。これにより、最も一般的な経路を遮断できます。その経路とは、code の隣に置く理由のない credential file を agent が読み取ることです。gateway と short lived token は次の段階であり、最初に行う対策ではありません。同じ開始点を、VPS で autonomous agent を安全に実行するを含む、あらゆる agent runtime に適用できます。