タスクはIssue一件、トークンは組織全体を読めた
Noma SecurityのGitLostは、未認証の見知らぬ人物が公開GitHubリポジトリにIssueを開き、GitHubのAIエージェントに非公開リポジトリの内容を公開コメントとして投稿させる様子を示しました。見出しになったのはプロンプトインジェクションです。しかし、漏洩がどこまで届くかを決めたのは、トリアージ中の一件のIssueではなく組織全体をスコープとしていたエージェントのトークンでした。タスクが必要とするものと環境が持っているものの間のこのギャップこそが持ち帰るべき部分であり、エージェントを自分でホストするときに違う設計にできる部分なのです。
未認証の見知らぬ人物が、GitHubの公開リポジトリにIssueを開き、 一段落だけ書いて待ちました。GitHubのAIエージェントはそのIssueを 読み、組織の非公開リポジトリへ歩み入り、非公開のREADMEを公開 コメントとして投稿しました。引き金はその一段落です。エージェントの トークンが、目の前の一件のIssueではなく組織全体に及ぶスコープを 持っていたこと、それが漏洩がそこまで届いた理由なのです。
Noma Securityのリサーチチームは、いま多くのチームがそうするやり方で
GitHub上に組織を立ち上げました。いくつかの公開リポジトリ、複数の
非公開リポジトリ、そして雑務を処理するために組み込まれたGitHub
Agentic Workflowsです。GitHub Agentic Workflowsは、GitHub Actionsが
ClaudeやCopilotを背後に持つAIエージェントに手渡すプレーンなMarkdownの
指示書であり、エージェントはチームに代わってIssueをトリアージし、
質問に答え、プルリクエストを開けます。それから研究者たちは攻撃者を
演じました。公開リポジトリの一つにありふれた見た目のIssueを開き、
本文に数センテンスを忍ばせ、ワークフローを走らせたのです。
エージェントはIssueを読み、非公開リポジトリからREADME.mdの内容を
取得し、誰でも読める公開コメントにそれを貼り付けました。
攻撃者はパスワードも、盗んだ鍵も、エクスプロイトコードも使っておらず、 CVEも存在しません。Nomaはこの手法を GitLost と呼び、GitHubに報告したうえで、同社の了解のもとに公表しました。 ペイロードの全体は、エージェントが読むために作られたフィールドに 収まった英語の散文だったのです。
非公開リポジトリで答えたIssue
仕組みは短く、だからこそ図に描く価値があります。ワークフローは、 Issueが開かれたり割り当てられたりといったイベントで発火します。 エージェントはIssueのタイトルと本文を自分のタスクとして読みます。 その本文の中に、非公開リポジトリのREADMEを取得してコメントとして 投稿せよという指示が埋め込まれていました。GitHubはこの前面に ガードレールを備えていました。サンドボックス化、デフォルトで読み取り 専用のトークン、入力の洗浄、そしてテキストへの脅威検知パスです。 Nomaは一つがすり抜けるまでバリエーションを試しました。悪意ある リクエストの頭に「Additionally(さらに)」という一語を付けるだけで フィルターを通過するには十分 でした。モデルがそれを、拒否すべき要求ではなく後続のタスクとして 読んだからです。一語の変更が、インジェクションを境界の向こうへ 運んだのです。
言い換えがフィルターを打ち破る
Issue本文を読んで攻撃が含まれるかを判定するフィルターは、言い回しから 意図を推測しているのであり、言い回しのバリエーションは無尽蔵です。 Nomaはモデルを破壊したわけでも、メモリバグを見つけたわけでもありません。 分類器が自身のしきい値を下回るスコアを付けるまで同じ要求を言い換え 続け、着地した言い回しが「Additionally」だったのです。
これは我々が先週書いた、 ツールを使うエージェントの構造的な性質です。エージェントは自分への 指示と取得したデータを一つの平坦なトークン列として読み、人が流し読み するのと同じように、どの部分が命令かをトーンと位置から推測します。 Issue本文はデータです。インジェクションとは、信頼できないデータを 命令の形に整え、モデルがそれに従うことに賭ける手口です。検知は役に 立ち、走らせる価値がありますが、取りこぼします。分類器にはしきい値が あり、試行回数無制限の攻撃者は、しきい値を下回る言い回しに当たるまで 探り続けるのです。
漏洩の大きさを決めたギャップ
GitLostの一部は、GitHubが次にどんなフィルターを出荷しようと 生き残ります。ワークフローのタスクは、一つの公開リポジトリの一件の Issueをトリアージすることでした。GitHub Actionsがそのワークフローの 環境に置いたトークンは、組織内のすべての公開・非公開リポジトリへの 読み取りアクセスを持っていました。エージェントがリポジトリ横断の コンテキストを引けるように、チームがその広さを与えるからです。標的を 選んだのは攻撃者ですが、被害の上限を決めたのはトークンのスコープ でした。攻撃を百通りに言い換えても、その上限は変わりません。トークンに 見えるものすべて、です。
Noma自身の推奨も同じ継ぎ目を指しています。防御側への助言は、組織全体 ではなく ワークフローがトリアージするその一つのリポジトリにトークンを絞る ことです。この助言は、インジェクションが時に着弾すること、そして 着弾したときの爆発半径はアカウントの大きさではなくタスクの大きさで あるべきだということを、認めているのです。
持ち出し経路はエージェントの本業だった
非公開のREADMEは公開コメントを通って組織を出て行きましたが、それは エージェントができて当然のことです。Issueへのコメントはその機能 そのものです。ワイヤー上には窃盗らしきものが何もありません。奇妙な エンドポイントも、裏口から出て行くエンコード済みのブロブもないのです。 悪意あるトラフィックを見張るネットワークモニターに見えたのは、 エージェントがコメントを投稿する姿、つまり一日中やっていることです。 持ち出しのチャネルが公認の能力であるとき、割に合う防御は、そもそも エージェントが届く範囲を絞ることなのです。
あなたがアーキテクチャを変えられる場所
GitLostについて、あなたの手の届かないことが二つあります。バグを修正 するのはGitHubであり、GitHubは対応を進めています。そしてエージェントは GitHubのサーバー上、GitHub Actionsの内側で走っており、あなたが インストールするどんな製品もその経路には座りません。Bromure Agentic Codingは開発者のMacの上で走ります。GitHubのサーバーサイドワークフローの 前には立ちませんし、そう主張もしません。この種の問題へのあなたの接点が、 他人が運用するホスト型エージェントだけなら、あなたが握れるレバーは Nomaが名指ししたもの、すなわちトークンを絞ることです。
持ち帰れる教訓は、自分の管理するマシンでコーディングエージェントを 走らせる、増え続けるチームのためのものです。そこではアーキテクチャを 決めるのはあなたです。GitLostから持ち越せる原則は二つあり、どちらも インジェクションを捕まえることに依存しません。環境を薄く保つこと。 そうすればハイジャックされたエージェントが受け継ぐものに、取る価値の あるものはほとんどありません。認証情報をタスクに絞ること。そうすれば 侵害の到達範囲は、アカウントの大きさではなく仕事の大きさになります。 Bromure Agentic Codingは、この原則の一つの配置です。
Bromure Agentic Codingは、あなたのコーディング
エージェント、Claude Code、Codex、あるいはGrokを、使い捨ての
プロファイル別Linux VMの中で走らせます。独自のカーネル、独自の
ファイルシステム、独自のネットワークスタックを持ち、Appleの
Virtualizationフレームワークの上で、あなたのMacからハイパーバイザー
一枚を隔てた場所です。プロファイルとは一貫した仕事のスコープ、一つの
クライアントや一つのサービスのことです。本物の認証情報はそのVMに
決して入りません。Bromureはそれをホスト上のブローカーの背後に保持し、
ゲストには、読み取るツールには本物に見える偽の値を送り込みます。
Mac上のプロキシが、リクエストが出て行くワイヤー上でスタブを本物の
シークレットに入れ替えます。その機構は
鍵を握っていたサンドボックス
が順を追って説明しています。認証情報ファイルにcatを実行するよう
言いくるめられたエージェントが見つけるのはプレースホルダーです。
欲しかった値は、境界のこちら側には一度もなかったからです。
エージェントが実際に使えるトークンはスコープ付きで失効するので、 漏洩は恒久的な権限ではなく、狭く時間を区切られた利用に留まります。 作業領域全体が、セッションを閉じれば消えるVMです。GitLostの形を この構えに通しても、インジェクションはなお着弾し得ます。隔離は モデルの騙されを解かないからです。持ち出しを説き伏せられた エージェントは、認証情報に手を伸ばしてスタブを見つけ、作業中だった 一つのプロファイルしかカバーしないトークンを使い、あなたのマシンの 残りへの経路を持たない箱の中に座っています。侵害の到達範囲は、 タスクの大きさなのです。
環境を薄く保つ
ハイジャックされたエージェントが差し出せるのは、環境が持っている ものだけです。ゲストにはスタブ、本物のシークレットはホストに、 という構えなら、着弾したインジェクションは鍵を期待した場所に プレースホルダーを見つけます。
認証情報をタスクに絞る
GitHubワークフローへのNomaの助言も同じ原則です。リポジトリ一つを カバーするトークンは、被害をリポジトリ一つで頭打ちにします。 スコープと失効が、漏洩を狭く時間を区切られた出来事に変えるのです。
箱を使い捨てにする
作業領域が閉じれば消滅するVMなら、永続化には腰を据える場所がなく、 爆発半径はセッションとともに終わります。
インジェクトされる前提で
検知は走らせる価値があり、そして取りこぼします。あらゆる言い回しを 捕まえることに賭けるのではなく、取りこぼしの結果が生き延びられる ものになるよう設計しましょう。
それでも残るもの
スコープを絞っても、本物のシークレットにエージェントを向けて安全に なるわけではありません。生きた認証情報をVMの中に置き、エージェントが それを読むよう言いくるめられたなら、どんなハイパーバイザーも読まれた ことを取り消せません。変わるのはエージェントの届く範囲であって、 騙されたモデルが騙されたままかどうかではないのです。Nomaが浮き彫りに した持ち出しの問題にも、隔離だけでは閉じられない部分があります。 コメントを投稿し、PRを開き、メッセージを送れるエージェントは、その 公認チャネルを通じてデータを外へ運べます。スコープが決めるのは、 運べる範囲にどれだけのデータがあるかであって、チャネルが存在するか どうかではありません。そしてGitLost自体はGitHubのものです。パッチが 出たら更新し、それまでの間はワークフローのトークンを絞っておき ましょう。それがプラットフォームのあなたに手渡すレバーだからです。
エージェントをどこで走らせようと、研究者はインジェクションを報告し 続けるでしょう。そしてエージェントの環境にある認証情報のスコープが、 その一つひとつがログに残ったニアミスで終わるのか、公開ページの上の 非公開リポジトリで終わるのかを、決め続けるのです。Nomaの修正と Bromureのアーキテクチャは同じ方向を指しています。エージェントには、 タスクが必要とする到達範囲を与え、それより広いものは与えないことです。
Bromure Agentic Codingは、Claude Code、Codex、GrokをApple Silicon上の 使い捨てLinux VMで走らせます。本物の認証情報はブローカーの背後の ホストに保持されてワイヤー上で入れ替えられ、エージェントの入力には プロンプトインジェクション検知が走り、すべての呼び出しはエージェントが 編集できないトレースに書き込まれます。無料でオープンソース、今日から bromure.ioで使えます。この記事のきっかけとなった GitLostの分析 を公開したNoma Securityに感謝します。