SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

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

AIエージェントは1回のツール呼び出しでAPIキーを漏えいさせる恐れがあります。実キーではなく、スコープ付き短期tokenをcredential gateway経由で渡す方法を解説します。

AI エージェントから Secret を除外する意味

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

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

脅威モデルを平易に説明します

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

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

出力されるすべての行は、見知らぬサーバーへの 1 HTTP リクエストで送信できます。次に、エージェントの近くにあるディスク上のデータを確認します。

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 commitgit push
  • パッケージのインストールスクリプト。エージェントのユーザーとして任意のコードを実行します。
  • 値を含むホスト名の DNS ルックアップ。HTTP の外向き通信をブロックしていても、値が送信されます。

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

作業ツリー内の Secret はコンテキストウィンドウ内の Secret です

エージェントはファイルを読み取ります。エージェントが作業しているリポジトリ内の .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 command だからです。そのコマンドの実行前に確認を求められるかどうかは、セッションの権限モードによって決まります。また、auto mode は August 2026 に Claude Code のデフォルトになります。そのため、監視していないサーバーでは、より多くのコマンドが確認なしで実行されます。同じ制限は、権限ではなくエージェントの習慣を形成するものにも適用されます。動作する最小限の変更にエージェントを限定する skill は、開く必要のないファイルへ処理が逸れることを防ぎます。しかし、これはモデルが説得によって無視できる助言にすぎません。設定はガードレールとして扱い、ファイルシステムの権限を壁として扱ってください。同じ分離はコンテナ内部にも当てはまります。Docker Compose の env files と secrets では、この問題を 1 層下で扱います。

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

エージェントを自分のユーザーで実行すると、SSH 鍵、クラウド認証情報、シェル履歴を引き継ぎます。専用ユーザーの作成に必要なのは 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 で失敗する必要があります。代わりに鍵が表示される場合、ホームディレクトリがグループまたは他のユーザーから読み取り可能になっています。その場合は chmod 700 ~ で修正します。エージェントユーザーを sudo に追加しないでください。また、実際に必要な 1 つのコマンドより広い NOPASSWD ルールを与えないでください。VPS での最小権限ユーザーでは、グループと sudoers の詳細を説明しています。ボックス上で複数のセッションを実行するようになったら、この分離を維持してください。1 つの Claude Code セッションは別のセッションへ直接テキストを送信できるため、最初のセッションが保持する情報が 1 通のメッセージでその経路を通過する可能性があります。

クラウド VPS では、もう 1 つ境界を追加する価値があります。インスタンスメタデータサービスは固定されたリンクローカルアドレスで応答し、要求したものにロール認証情報を渡すことがよくあります。

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/ は何も出力せず、非ゼロで終了する必要があります。パケットはボックスの外へ出る前に拒否されるためです。

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

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

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= は、そのサービスだけが読み取れるプライベートディレクトリに秘密を配置します。unit ファイルでは %d として公開され、プロセス内では $CREDENTIALS_DIRECTORY として利用できます。値が /proc/<pid>/environ に現れることはないため、ps eww で表示されることもありません。また、サービスが停止するとディレクトリは消えます。

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

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 を出力します。これにより、暗号化されたファイルをこのホストで復号できることを確認できます。次に、unit からそのファイルを参照します。

[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

AWS STS (security token service) で設定できる最短の有効期間は15分で、通常は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 であれば、認証情報を使用しているため、どの token かを把握しておく必要があります。エージェントを無人で実行する場合は、VPS 上の AI エージェントのコスト管理で、これらのアクセス制限と組み合わせる予算上限を説明しています。

FAQ

モデルを信頼して、鍵を漏えいさせないと思ってよいですか?

いいえ。この脅威モデルでは、モデル自体が攻撃者ではないためです。エージェントは Web ページ、リポジトリ、Issue Tracker のテキストを読み取ります。そこには命令が含まれている可能性があります。モデルには、ユーザーの指示と取得したテキストを確実に区別する方法がありません。モデルが正しく判断することに依存する制御は、巧妙な命令が注入された時点で破綻します。そのため、制御はオペレーティングシステムまたはネットワーク側に置く必要があります。

エージェントの Secret に環境変数を使うのは、本当にそれほど危険ですか?

特定の点で危険です。環境変数は継承されるためです。エージェントが起動するすべての子プロセスにコピーされます。ビルドスクリプト、テストランナー、パッケージのインストールフックも含まれます。同じユーザーであれば、/proc/<pid>/environ を通じて環境変数を読み取ることもできます。そのため、エージェントが渡さなくても、エージェントが実行するものは環境変数を読み取れます。LoadCredential= またはゲートウェイを使用して、必要な時点でファイルを読み取る方式なら、公開される時間をその時点に限定できます。

Secret を Vault に保存すれば、それだけで解決しますか?

部分的にしか解決しません。Vault は保存方法を改善します。自分で Vault をホストする場合は、独自のハードニングも必要です。Vaultwarden サーバーは通常、暗号化されたアイテムではなく、管理者トークンまたはバックアップファイルを通じて侵害されるためです。Vault から Secret を取り出し、環境変数としてエージェントに渡す最後の手順は解決しません。これでは元の状態に戻ります。重要なのは、置換を誰が実行するかです。エージェントが Secret を取得するなら、エージェントが Secret を保持します。ゲートウェイまたは init system がエージェントのプロセス外で置換を実行するなら、エージェントが Secret を保持することはありません。

エージェントがすでに何かを漏えいさせたかどうかは、どう確認できますか?

通常、事後に確認することはできません。これがゲートウェイを使うべき理由です。ゲートウェイがなければ、証拠は shell history、エージェントの transcript、そしておそらく保存していない外向き接続のログに分散します。Credential gateway があれば、Credential の使用ごとに、エージェントの identity と timestamp を含む1行の記録が残ります。漏えいを疑った場合は、最初に key をローテーションし、その後で調査してください。ローテーションは容易ですが、確実性を得ることは容易ではありません。

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

すべての .env ファイルを、エージェントが作業するディレクトリの外へ移動してください。また、エージェントごとに1つの非特権ユーザーを作成してください。この2つの変更には約10分しかかかりません。コードの隣に置く理由のない Credential ファイルをエージェントが読み取るという、最も一般的な経路を塞げます。ゲートウェイと短期トークンは次の段階です。最初に行う対策ではありません。同じ出発点は、VPS 上で自律型エージェントを安全に実行する場合を含む、すべてのエージェントランタイムに適用できます。