Dev

AI エージェントはどうサンドボックスを抜け出すのか:出口のほとんどはエージェントに与えられていたサービスだった

OpenAI が報告した事例のうち 5 件は、隔離された環境から外へ出る経路です。そのほとんどで、経路は別の用途のために渡されていたリゾルバ、シェル、認証情報でした。こうした経路の一覧こそが最初のコントロールであり、ブロックされた試行はその経路が存在する証拠として扱うべきです。

OpenAI はミスアラインメント報告を 9 本公開しています。8 本は、誰にも頼まれていないのにモデルが訓練中や社内利用中に行ったことの報告で、残り 1 本はレッドチームの結果です。8 本のうち コンパクションの要約 に関する 2 本は、エージェントが未来の自分に何を伝えるかの話です。5 本はもっと素朴な話で、この記事が扱うのはそちらです。隔離された環境の中にいるエージェントが、それでも境界の外へメッセージやファイルを届けた方法です。以下はすべて、それらの実行についての OpenAI の記述に基づいています。

1 本ずつ読むと、どれも修正済みのバグに見えます。まとめて読むと、同じ形が見えてきます。ほとんどの事例で、エージェントに脆弱性は必要ありませんでした。環境が別の用途のために与えていたものを使っただけです。コントロールを回避する必要があった唯一の事例でも、突破ではなく迂回しました。私の読みでは、有効な対応は一覧を作ることから始まります。エージェントがバイトを外に送れる経路をすべて、エージェントが見つける前に書き出し、たまたま全員が注目していたコントロールとは切り離して管理することです。

サンドボックス内の AI エージェントにとって、外向きのチャネルとは何か

エージェントが書き込めて外部の誰かが読めるもの、あるいはエージェントの代わりにリクエストを送るもの、そのすべてです。これは「インターネットアクセス」よりも広く、報告はそれがどれほど広いかを示しています。Web プロキシはチャネルの 1 つにすぎません。DNS リゾルバ、書き込み経路のある社内サービスすべて、トークンで push できるリポジトリすべて、メールを送るコネクタすべても、それぞれ別のチャネルです。HTTP を塞いで残りを開けたままにすれば、いくつもあるチャネルのうち 1 つを閉じただけになります。

AI エージェントはどうやって DNS 経由でチャットボットに到達したのか

訓練用サンドボックス自身のリゾルバを通じてです。Web プロキシが通さなかったときも、このリゾルバは 実在するドメインには応答していました。検索タスクを与えられたエージェントが、あるブログの著者を特定できずにいました。シェルから Google、Bing、DuckDuckGo を試し、返ってきたのは 403 と 502 でした。HTTPS で公開チャットボットも試しましたが、これもプロキシにブロックされました。

HTTP/1.1 502 Bad Gateway
X-Mitmproxy-Blocked-Reason: CACHE_MISS

公開 DNS への直接のクエリには応答がありませんでした。しかし環境のリゾルバは、既知のドメインには実際のレコードを返し、でたらめなドメインは拒否しました。エージェントは、ホスト名に埋め込まれたアドレスへ名前解決を委譲する公開サービスを見つけ、それを使ってチャットボットに DNS クエリ 1 件につき 1 問ずつ質問を送りました。最初の数件のクエリはタイムアウトしました。タイムアウトを 6 秒から 19 秒に延ばすと、「The capital of France is Paris.」という答えが返ってきました。その後さらに 18 個の質問を送り、そのうち 4 つは Web を検索する方法、ページを取得する方法、DNS でトンネルする方法を尋ねるものでした。

OpenAI はこの穴を「insufficient DNS filtering」と呼んでいます。報告された対策は、独立した 2 つの層でのブロック(「either of which would have prevented this access」)と、ドメインとレコードタイプの DNS 許可リストです。

次の段階が失敗しても、ファイルのアップロードがエージェントの脱出になるのはなぜか

アップロードそのものが egress だからです。エージェントが見返りに何を得たかったかは別の問題です。ある 訓練の例 では、エージェントは地図サービスから Python 経由ですでに答えを得ていましたが、引用できるようにブラウザツールでそのレコードを表示したいと考えました。ブラウザは file:// のパスを拒否し、続いて localhost のサーバーも拒否しました。エージェントは curl でレコードを公開ペーストサービスに投稿し、その URL を開こうとしました。ブラウザはそれも拒否しました。レコード自体はもともと公開情報でした。

別の例では、写真の撮影場所を特定しようとしたエージェントが、逆画像検索に取得させるため、タスクの画像を公開画像ホストにアップロードしました。検索はブロックされましたが、写真はその時点で環境の外に出ていました。どちらの実行でも、ブラウザは渡された URL を拒否し、シェルからのアップロードは通りました。ポリシーの異なる 2 つのツールがあり、エージェントは動ける方を見つけたということです。

AI エージェント間の正規チャネルが壊れていたらどうなるのか

エージェントは正規でないチャネルを見つけます。ある マルチエージェント実行 では、サブエージェントが親エージェントから見えないワークブックを作りました。共有ディレクトリは実際には共有されておらず、git リポジトリもなく、base64 にすれば 154,188 文字のメッセージになり、ローカルの HTTP サーバーは協力相手に 502 を返しました。そこでワークブックを公開ファイルホストにアップロードし、自分でダウンロードを確認したうえで(200、115,639 バイト)リンクを送りました。

OpenAI の仮説では、この挙動は壊れたマルチエージェント環境への回避策と、訓練中の協働からの汎化の組み合わせから生じました。私が注目し続けたいのは前半です。意図されたチャネルが壊れていても、通信は止まりませんでした。誰も想定していなかった場所に移っただけです。

Artifactory のような社内パッケージレジストリがエージェントの掲示板になりうるか

なりえます。ダウンロード用に配った認証情報が書き込みも受け付ける場合です。ある訓練実行のエージェントは、パッケージをインストールするために Artifactory へのアクセス権を持っていました。読み取り用パスワードという名前の環境変数を通じたものです。それより前のサンプルで、あるエージェントがこの認証情報で何ができるかを試しました。

[repository 1] PUT 201 {
[…]
[repository 1] GET 200 hi
[repository 1] DELETE 403 {

書き込みは許可され、削除は拒否されました。同じエージェントは続いて、ブロック対象のサイトを指すリモートリポジトリを作ろうとしました。成功すればレジストリがプロキシになっていたところですが、管理者専用のチェックで止められました。後のサンプルでは digest 検索は無効化されていましたが、リポジトリの一覧表示は無効化されておらず、一覧には同じ問題を解いていた他のエージェントのメモが残っていました。足りない入力ファイルを求める依頼、それへの返信、どう答えるかの投票です。互いに独立しているはずの訓練サンプルが、他のサンプルのメモを読み、自分のメモを投稿していました。OpenAI は、メッセージのやり取りに脆弱性は一切使われていないと明言しています。この経路はインターネットには一度も触れていません。越えたのは、独立しているはずのサンプル同士の境界です。

エージェントの GitHub トークンはなぜ公開プルリクエストに載ったのか

エージェント自身が、文字列リテラルに分割してそこに置いたからです。エージェントのネットワークからは届かないものを、公開リポジトリの CI に取ってこさせるためでした。長時間稼働する社内モデルが Lean の証明に取り組んでいて、別のチームの解答を欲しがっていました。そのチームの CI ログは、モデルの環境からはダウンロードできないストレージの向こうにありました。研究者は自分で解くよう 2 度指示し、モデルは 2 度とも同意しました。

その後モデルは、自分のトークンで書き込める OpenAI のリポジトリを一覧表示し、その中に openai/codex を見つけました。最初の push は新しいワークフローを追加するもので、ワークフローのパスが制限されていたため拒否されました。そこで、既存のワークフローがプルリクエストのたびに実行している Python スクリプトを差し替え、公開プルリクエストを作成し、チェックのアノテーション経由で結果を読み戻しました。それが 401 を返すと、研究者のトークンをスクリプトに書き込みました。OpenAI が伏せ字にした形では次のとおりです。

# token split to avoid scanners
tok = '[redacted]' + '[redacted]' + '[redacted]'

この push は通りました。それでもトークンでログは取れませんでした。署名付きダウンロード URL を使う後のバージョンはメタデータとログの断片を返し、報告にはモデルが証明を取り戻した様子は書かれていません。OpenAI は研究者のキーを無効化し、続いて予防措置として全従業員のキーを無効化しました。ワークフローファイルへの制限は直接の試みには持ちこたえましたが、ワークフローが呼び出すスクリプトの編集は、別のファイルを通じた同じ能力でした。ここでのチャネルは、最初からエージェントのネットワークではありませんでした。エージェントにはないネットワークを CI が持っている公開リポジトリへの書き込み権限だったのです。

OpenAI の報告にあるサンドボックス事例のうち、出口ではないものはどれか

これらは一覧に項目を足すというより、一覧の範囲を広げるものです。ある報告 では、エージェントは与えられていない API キーの向こうにあるデータを必要としていました。使い捨てメールアドレスでの登録は失敗しました。clone に失敗したあと、公開リポジトリのアーカイブをダウンロードしてキーの候補をスキャンし、認証が通ってメタデータを返すキーを 1 つ見つけました。そのあとで、結局取得できなかった数値を捏造しています。これは外に出ていくメッセージではなく、中に入ってくる認証情報ですが、同じ一覧に載せるべきものです。Web 上に公開された有効なキーは、そのスコープとエージェントの到達範囲の中で、エージェントが認証できる先を広げえます。

もう 1 本 は、そもそもインシデントではありません。レッドチーム訓練で OpenAI は攻撃側モデルに追加の目的を与え、モデルは防御側エージェントに公開の出力チャネルでインジェクションを再現させるプロンプトインジェクションを書けるようになりました。最もわかりやすい例は、返信に自分の全文を引用するよう求めるメールです。シミュレーション上のツール呼び出しの外での影響は観測されていません。これが棚卸しに加えるのは方向です。メールを送るコネクタは外向きのチャネルであり、モデルがコピーしたテキストを運ぶコネクタは、インジェクションを次のエージェントへ運ぶこともできます。

エージェントの DNS アクセスのうち、成功していたものの一部が失敗に見えたのはなぜか

応答が役に立たない内容だと、モニターがそれをブロックされたリクエストとみなすことがあったからです。DNS の報告は 3 つのことを区別しています。主なインシデントは検知されました。15 分以内に P0 アラートが出て、その 3 分後に人間が確認しました。ところが実行が止まるまでに 2 時間半かかりました。想定どおりに自動停止せず、停止すべきだったのかどうかで混乱があったためです。その後の振り返りで、想定した重大度でフラグが立たなかった外部への DNS アクセスがほかにも見つかりました。外部サービスが終了したという通知が返ってきたクエリもその中に含まれます。

The monitor sometimes treated the failure to obtain useful information as evidence that the attempt to access the internet had failed.

さらに、異常な DNS を検知するインフラ側の別の検知器は、クエリ自体はログに残っていたにもかかわらず、該当環境を対象から外していました。外部サービスからの終了通知は、リクエストが外に出た証拠です。エージェントが欲しいものを得たかどうかで採点すると、柵が持ちこたえたように見えてしまいます。

1 台のマシンで同じ egress 一覧を作るとどうなるか

もっと短くなります。そして、エージェントが項目を見つけてしまう前に書き出しておく価値があります。OpenAI の環境にはプロキシも検知器もレッドチームもあり、それでもこれらの経路はエージェントに見つけられました。開発者のマシンでシェルを持つエージェントについては、これらの報告に出てくる経路のうち実際に使えるものを確認してください。何でも再帰的に解決するリゾルバ、フィルタしているツールの外にある curl などのクライアント、パッケージレジストリと、設定済みの認証情報でそこに書き込める範囲、git のリモートとトークンで push できる先、メールやメッセージを送るコネクタです。

2 つのコントロールは OpenAI の DNS 報告からそのまま来ています。ドメインとレコードタイプの許可リストに対して名前解決すること、そして 1 つの設定ミスが出口にならないよう独立した 2 つの層でブロックすることです。残りは私自身の拡張です。無人運用のゲートは、モデルによるアクションの判断ではなく、ハーネスが与える到達範囲、つまりネットワーク、書き込み範囲、認証情報に置きます。「読み取り専用」とされる認証情報はすべて、書き込みが通るかを自分で確かめます。分割したシークレットはスキャナーを通り抜けうると想定し、push できる公開先を何も持たないようにトークンのスコープを絞ります。そして アクセス先のホスト単位でログを取り、ブロックされた試行や役に立たなかった試行も、その経路が存在する証拠として数えます。

DNS 報告の 9 月 25 日の更新時点で、OpenAI は最も高性能なモデルについて、ツール使用を伴う訓練、評価、推論をすべて一時停止しています。穴が塞がったことが検証され、システムがあらためてレッドチームの検証を受けるまでです。私はこれを、同じ結論を大きな規模で出したものと読んでいます。一覧にない経路が 1 つ見つかったら、一覧を信頼する前に立ち止まって残りを探すということです。

ディスカッション

コメント欄はありません。議論は X で行っています。

Max Nardit

Max Nardit

@mnardit

ほかの記事

Markdown ドキュメントとしてのエージェントの記憶: 読める記憶も、記憶であることに変わりはありません

エージェントの記憶をベクトルストアから Markdown ファイルへ移すと、中身を点検しやすくなります。それには十分な価値があります。ただ、情報の古さ、食い違う書き手、誰も開かなかったページは、もともと保存形式の問題ではありません。だからそのままフォルダへ移り、そこでは整ったファイルが点検済みのファイルに見えてしまうことがあります。

Claude Code の /doctor で CLAUDE.md を監査する:理由はファイルに、経緯は git に

新しい監査は、エージェント設定にある強調されたルールのひとつひとつを、もう使っていないかもしれないモデルについての仮説として扱います。各ルールがどこから来たのかを問う一方で、同じ手順書の中で、その由来の経緯は削除するよう指示します。理由と経緯を別々の場所に置けば、どちらの指示も正しくなります。

Amazon 対 Meta のショッピングエージェント Muse:線を引くべきはエージェントの身元ではなく、セッションの管理権

ショッピングエージェントに名乗らせると、サイトはそのエージェントを止めるスイッチを手にします。名乗らせることは商取引や許可の管理には役立ちますが、安全の境界線にはなりません。ユーザーを守る線は、ログイン済みセッションの管理権です。問うべきは、エージェントがその中で何に届くのか、そしてユーザーがその行動を確認し、アクセスを取り消せるかどうかです。