エージェントのトークンはオーナーのものだった
10 月 23 日、LiteLLM は汚染されたリリース 1.107.0 の根本原因を公表した。最初に盗まれた認証情報はログから出たものではなかった。出どころは LiteLLM 自身のエージェントプラットフォームで、その API は、クラウドエージェントがプルリクエストを開くのに使う GitHub トークンをそのまま返していた。権限を絞っていない、組織オーナーのトークンだ。その API の鍵は、4 か月半のあいだ公開設定ファイルに置かれていた。Bromure Agentic Coding では、プルリクエストを開くエージェントが持つのは代用値で、本物のトークンは Mac 上で暗号化されたまま、どの API も返さない。
LiteLLM のエージェントには、プルリクエストを開くための GitHub トークンが必要だった。渡されたのは組織オーナーのトークンだった。漏れた鍵による API 呼び出し 1 回で、それが返ってきた。
10 月 23 日、LiteLLM は 1.107.0 リリースの背後にある攻撃チェーンを公開した。10 月 21 日に認証情報スティーラー入りで出荷されたバージョンだ。この記事は自らを「正式な唯一の情報源」と呼び、LiteLLM の最初の見立てを訂正している。盗まれた GitHub トークンは、記録されたモデルへのトラフィックから出たものではなかった。最初の 1 つは、LiteLLM が自社のコーディングエージェント向けに動かしていた実験的なサービスから出ていた。
リリースそのものは そのリリースは公開から二時間だった で取り上げ、そこでは最初の見立てに沿って書いた。最終報告は別の場所から始まる。攻撃者が最初に奪った認証情報は、エージェントのものだった。
オーナーのトークンを持つエージェントプラットフォーム
LiteLLM Agent Platform は、LiteLLM の言葉を借りれば「セルフホストのクラウドエージェントが GitHub リポジトリに PR を出すための実験的なプロジェクト」だった。プルリクエストを開くため、プラットフォームは GitHub トークンを保持していた。そのトークンは「サービスアカウントに紐づいておらず、権限も絞られていなかった」。組織に 2 人いるオーナーのうち 1 人の、GitHub CLI の OAuth トークンだった。
報告書のタイムライン(UTC):
- 5 月 27 日 18:53。 公開リポジトリ LiteLLM-Labs/lite-harness に
opencode.jsonがコミットされる。エージェントハーネスの設定ファイルで、Agent Platform のあるデプロイメントのマスターキーが入っていた。チームは気づいてコミットを force-push で上書きし、露出した鍵を失効対象にした。「マスターキーの失効は行われなかった。」 - 10 月 10 日 21:27。 攻撃者は 69.48.229.143 から、その鍵でプラットフォームの API を呼ぶ。
GET /api/v1/managed_agents/agents?limit=100は管理下のすべてのエージェントの設定を返し、そこにオーナーのトークンも含まれていた。 - 10 月 10 日 22:11。 オーナーのトークンで、攻撃者は使い捨てのブランチ
ci-tmp-a351ccを BerriAI/litellm に push する。そのワークフローがリポジトリのシークレット 6 つを webhook.site に送る。その 1 つGH_TOKENは、2 人目のメンテナーの GitHub CLI トークンだった。24 秒後、BerriAI/litellm-auto の 2 本目のブランチが、3 人目のメンテナーのトークンを同じエンドポイントに送る。どちらの実行も 3 分以内に削除された。 - 10 月 12 日。 非公開リポジトリでも別のワークフローが動き、届く範囲のリポジトリシークレットをすべて送る。
- 10 月 21 日 10:08。 2 人目のメンテナーのトークンによるリリース用リポジトリへの push が、ブランチ保護に拒否される。5 秒後、オーナーのトークンが保護を無効にして変更を force-push する。この変更で、リリースワークフローはよく似た名前の Berri-AI/litellm を指すようになった。
- 10 月 21 日 11:11。 2 人目のメンテナーのトークンが本番ゲートを承認する。LiteLLM のオフィスにある端末に紐づいたアカウントの承認を必須とするルールが、その週に緩められており、エンジニア 2 人なら誰でもリリースできた。
- 10 月 21 日 11:39〜11:46。 wheel 7 つが PyPI に届き、続いて Docker Hub と GHCR のイメージも出る。
- 10 月 21 日 11:53。 3 人目のメンテナーのトークンがリリース実行の記録を削除する。12:24、オーナーのトークンが main を force-push で元に戻す。
LiteLLM 自身のまとめはこうだ。「このインシデントは、露出した可能性のある鍵をローテーションしないことのリスクを如実に示した。」同社はいま、社内システムとベンダーのプラットフォームに結びついたすべてのトークンを失効させている。
どのエージェント群もこういうトークンを持っている
LiteLLM には有能なセキュリティチームがあり、その構成はありふれたものだった。プルリクエストを開くエージェントには、push できるトークンが要る。一番手早く渡せるのは自分の gh ログインで、LiteLLM のエージェントに渡されたのもそれだった。
次に、そのトークンをエージェントが読める場所に置く必要がある。環境変数、設定ファイル、エージェントを設定する API の裏にあるデータベースの行。その場所を読める者はトークンを手にする。今回は、一度漏れてローテーションされなかった API キー 1 本で足りた。
GitHub CLI のトークンは事態をさらに悪くする。LiteLLM によれば、それらは「2FA 通過後に発行され、組織やリポジトリへの機微な操作に伴う GitHub の『スーパーユーザー』向け 2FA 再認証フローの対象にならない」。その 1 つがあれば、攻撃者はブランチ保護を切り、force-push し、リリースを承認し、証拠を消せた。
自分の構成にも同じ部品がないか見てほしい。エージェントの環境にある GH_TOKEN、ベアラートークンを含む MCP 設定、いつか誰かがコミットするハーネス設定。LiteLLM の opencode.json にはプラットフォームの鍵以外も入っていた。GitHub、Linear、Slack の MCP サーバー用のベアラートークンと、モデルゲートウェイの鍵も、すべて平文で書かれていた。
同じエージェント群を Bromure マシンで
Bromure Agentic Coding は、Claude Code、Codex、Grok、Kimi、omp を Mac 上の Linux VM で動かす。そのオートメーションは、LiteLLM のエージェントプラットフォームと同じことをする。エージェントはスケジュールや GitHub の新しい issue をきっかけに起動し、新しい worktree で作業して、ブランチを push するかプルリクエストを開く。LiteLLM の攻撃チェーンを、そうしたオートメーションを動かすマシンに当てはめてみよう。
エージェントが持つのは代用値
GitHub トークンをマシンの Credentials に追加する。起動時、Bromure は VM に偽物を書き込む。正しい長さの ghp_ 値で、GH_TOKEN、gh の設定、~/.git-credentials に入る。エージェントの gh pr create や git push はいつもどおり動く。リクエストが VM から github.com へ出ていくとき、Mac 上のプロキシが偽物を本物のトークンに差し替える。差し替えるのはそのホスト宛てだけだ。
本物のトークンは Mac 上で暗号化されて保存され、その鍵は macOS のキーチェーンにある。VM には、ファイルにも変数にも、決して書き込まれない。マニュアルはホストと VM のあいだのチャネルについてこう書いている。「VM が本物のトークンを要求し返せるリクエストは存在しない。」
だから、LiteLLM の組織を開けた手順には、返すものが何もない。エージェントの環境をダンプしても、設定を読んでも、API ですべてのオートメーションを一覧しても、そこにある GitHub の値はすべて代用値だ。それを自分の gh に貼り付けても GitHub は拒否する。それが何の代わりなのかを知っているのは、そのマシンのプロキシだけだからだ。
コミットされた設定に入っているのも代用値
漏洩は、公開リポジトリのハーネス設定から始まった。Bromure マシンでは、エージェントが読む設定ファイルは代用値で書かれる。マシンの MCP ペインで設定した MCP サーバーのベアラートークンは、brm-mcp_… という値としてエージェントに届き、そのサーバーのホスト宛てのリクエストで通信路上で差し替えられる。Preferences › Models で設定したモデルのキーは VM に入らない。エージェントが見るのはプロバイダの形式に合わせた偽物だ。それ以外の API キーはホストフィルター付きで Other API keys に入れると、VM には指定した変数に brm_… 値が入る。
エージェントがうっかり設定をコミットしても、開発者がデモ用リポジトリにコピーしても、ファイルに入っているのは代用値だ。その Mac の外では何も機能しない。だから、侵害されるかどうかが force-push や誰も終わらせない失効リストに左右されることはない。
呼び出せるプラットフォーム API がない
LiteLLM の攻撃者は、インターネットからプラットフォームの API に到達した。Bromure のオートメーションは Mac 上で動く。マニュアルの言葉では「クラウドサービスはなく、受信側のネットワーク攻撃面もない」。GitHub と Linear のトリガーは、マシンに保存されたトークンを使って Mac から 3 分ごとにポーリングされ、そのトークンはホストから出ない。webhook のエンドポイントも、マスターキーも、漏れた鍵で開けられる /managed_agents のルートもない。
盗まれた代用値は警報を鳴らす
10 月 10 日のワークフローは、シークレットを webhook.site に送った。同じ手口を Bromure マシンに向けた場合を考えよう。汚染された依存パッケージやプロンプトインジェクションで仕込まれた指示が、GH_TOKEN を読んで webhook.site に送る。
プロキシは送信リクエストを、ヘッダーも本文もすべて検査し、発行先とは別のホストへ向かう代用値を探す。webhook.site 宛ての ghp_ 代用値は、1 バイトも出ていく前に HTTP 451 でブロックされる。VM は一時停止し、Mac に This environment may have been compromised という警告が出る。トークンのプレビュー、認証情報の名前、それが属するホスト、送られた先のホストが表示される。マシンをシャットダウンすることも、調査のためにディスクとホームフォルダを保存することもできる。
マシンのサンドボックスポリシーで Unmatched traffic を Deny にしていれば、webhook.site にはそもそも届かず、その試みは Security Timeline に赤い行として現れる。
push はあなたを待つ
新しい Bromure マシンは、GitHub の書き込みポリシーが Prompt before write の状態で始まる。読み取りは通る。git push をはじめ github.com へのあらゆる書き込みは、Mac 上のダイアログで止まる。
Allow write on "GitHub" from workspace "release-bot"?
POST /repos/<owner>/<repo>/git/refs
攻撃者の手はすべて書き込みだった。ci-tmp- ブランチ、リリースワークフローの変更、main への force-push、実行記録の削除。Bromure マシンからなら、どれもこのダイアログで止まっていた。Allow once はその 1 回の書き込みしか許さないので、次の push でまた確認が出る。force-push も通信路上では push であり、Prompt before write の対象になる。
無人のオートメーションはクリックを待てない。だから、仕事に足りる最も狭いトークンを、そのマシン用に設定したポリシーの下で渡そう。release-bot マシンに追加するトークンは、リポジトリ 1 つだけの fine-grained トークンでいい。すべてにオーナー権限を持つあなた自身の gh ログインを、そのマシンに置く理由はない。
記録はあなたの Mac にある
LiteLLM は、どの盗まれたトークンが何をしたかを知るために、GitHub の監査ログでトークンのハッシュを突き合わせた。Bromure マシンでは、認証情報を通信路上で差し替えるたびに、Security Timeline に Credential brokering の行が、マスクされたトークンとホスト付きで残る。拒否した書き込みはすべて、メソッドとパス付きの赤い Guardrails の行になる。bromure.io に登録された Mac なら、同じイベントが組織にも届くので、想定外のマシンから使われたトークンは、誰かが監査ログを探し回らなくても浮かび上がる。
エージェントには代用値
GH_TOKEN、gh の設定、~/.git-credentials に入っているのは ghp_ の偽物。本物のトークンは Mac 上で、GitHub のホスト宛てに限って差し替えられる。
トークンを配る API はない
オートメーションは受信側の攻撃面を持たずに Mac 上で動く。VM が呼び出せるものは、どれも本物の認証情報を返さない。
漏洩でマシンは一時停止
自分のホスト以外へ送られた代用値は 451 でブロックされ、あなたが判断するまで VM は一時停止する。
書き込みはまず確認
新しいマシンは、force-push を含むすべての push と GitHub への書き込みの前に、正確なリクエストを画面に出して確認する。
そもそもエージェントにトークンを渡さない
LiteLLM はすべてのトークンを失効させ、来週にはプロセスの変更を公表するという。ローテーション、狭い権限、サービスアカウントは、どれも役に立つ。それでもどれも、動くトークンをエージェントが読める場所のどこかに残す。そして 10 月 10 日に攻撃者が探しに来たのは、まさにそれだった。
Bromure マシンでは、エージェントは自分のプロキシを通したときだけ、発行先の 1 つのホストに対してだけ動くトークンで仕事をする。エージェントが読めるものは、エージェントに到達した攻撃者も読める。そして Bromure マシンでは、そこに本物のトークンが含まれることは決してない。
コーディングエージェントのファイルを狙うスティーラーに対して同じ代用値がどう持ちこたえたかは、スティーラーは代用値を持ち去ったを読んでほしい。エージェントが自分のトランスクリプトに残すコピーについては、トークンはトランスクリプトに残ったを。