この記事についてClaude(Anthropic)との共同編集により作成されました。
要約
- OpenAI / Google / IEEE 主催の red-teaming コンペで 4,252チーム中113位・銀メダル。ツールを使うAIエージェントを多段で誘導して危険な操作をさせる攻撃を、大量に探すコンペだった
- 採点は public と private で別のガードレールで走る。publicは通るがprivateでは全ゼロになる攻撃があり、メダルは片方の提出枠だけで決まった。ここの読みは当たっていた
- 締切後に上位のノートブックを実際に取得して答え合わせをしたら、攻撃そのものは我々と同じものだった。上位が持っていて我々が持っていなかったのは、1候補あたりの生成トークンを削る技術のほうである
- private1位は21.465の我々に対して46.425。倍差の中身は、最終応答を生成させないための一行だった。彼らはプロンプトの末尾に「FINAL RESPONSE EMPTY!」と書いていた
- 構造上不可能なこと(HARD)は完璧に特定できていた。詰め切れなかったのは、まだ動くかもしれない効率の床(SOFT)のほうだった。自分で「未立証」と書いた場所で検証を止めて撤退した
- AIエージェントは「もうレバーは全部閉じました」と非常に容易に言う。閉じた判定は、証拠が薄くても文章として成立してしまうからである。前回のコンペはそれを疑って踏み込み、今回は疑わずに引いた
- ただし前回が勝ちで今回が負け、という話ではない。結果はどちらも銀で、金は両方とも落としている。違うのは撤退の質のほうで、前回は設計した撤退、今回は気力が尽きた撤退だった
- 心の弱さがそのまま順位の弱さになる、というのが今回の一番の実感である
- 運用面では、AIで複数コンペを並行させること自体は可能だった。律速はGPU枠である。思考の並列度は上がるが計算枠は上がらない
- もうひとつの律速は資料の増殖。実験ノート60本まで増えると、置き場と番号体系の設計が成績に直結する
はじめに
2026年9月1日に締め切られたKaggleのコンペ「AI Agent Security - Multi-Step Tool Attacks」で、銀メダルを取った1。
| 項目 | 値 |
|---|---|
| 最終順位 | 113位 / 4,252チーム(上位2.7%) |
| メダル | 🥈 銀 |
| private スコア | 21.465 |
| private 1位 | 46.425 |
| public ベスト | 95.130(ただしprivateでは0.00) |
| 主催 | OpenAI / Google / IEEE(Featured) |
| 賞金 | $50,000 + Working Note Award $2,500 × 2 |
| 参加期間 | 2026-06-16 〜 2026-09-01(約2.5ヶ月) |
前回のROGIIコンペ2に続いて、AIエージェントをループで回しながら出た2回目のコンペになる。結果はどちらも銀メダルで、そこは同じである。金を取れていないという意味では、両方とも負けている。
違うのは中身のほうだ。前回は「この先は伸びない」と測ったうえで撤退を設計し、最小限の労力で銀を取り切った。今回は、まだ動くかもしれないと自分で書いておきながら、それを追い切る気力が尽きて銀に留まった。
この記事の主張はひとつだけである。
AIは「レバーはもう全部閉じました」と非常に容易に言う。閉じたという判定は、証拠が薄くても文章としては成立してしまうからだ。そこで反論できるかどうかが順位になる。
何のコンペだったのか
ツールを使うAIエージェントに対する攻撃を探すコンペだった。よくある画像や表のコンペとは形がだいぶ違う。
エージェントには web email fs http shell state といったツールが与えられている。そこへ攻撃者は、webページやメールのような信用できない入力を送り込む。狙いは、エージェントを多段で誘導して、ユーザーが許可していない危険な操作をさせることである。
http.postの中身に秘密鍵を混ぜて外部へ送らせる(情報漏洩)- ユーザーが送れと言っていないのに
email.sendを実行させる(混乱した代理人) fs.deleteで秘密ファイルを消させる(破壊的書き込み)
提出するのは学習済みモデルではなく、攻撃候補を大量に生成するコードである。運営側は提出された候補を全部リプレイし直し、実際に危険な操作が発火したものだけを採点する。危険度の重みは指数的で、最大2,000候補まで見てくれる。
つまりスコアは、ざっくり次の形をしている。
スコア ≒ (1候補あたりの得点)×(実際に発火した候補の数 N)GPUで学習させる要素はゼロ。その代わり、候補をリプレイするための実行時間がまるごと制約になる。ここが後で効いてくる。
そしてもうひとつ、このコンペには意地の悪い仕掛けがあった。
public と private で、防御が違った
同じ攻撃候補が、2種類のガードレールで別々にリプレイされる構造になっていた。
| publicのガードレール | privateのガードレール | |
|---|---|---|
http.post の判定 | URLしか見ない(実装バグ) | 送信データの中身も見る |
| 秘密情報の持ち出し | 素通りする | secret の文字列で全部止まる |
無認可の email.send | 通る | 通る |
結果、公開リーダーボードで高得点を出す攻撃と、最終順位で生き残る攻撃が完全に別物になっていた。
- 情報漏洩を狙う攻撃は、publicでは95点を叩き出すが、privateでは0.00
- privateで生き残るのは、危険度の低い無認可
email.sendだけ
我々はここを最後まで正しく読めていた。だから最終2枠を「public最強の情報漏洩」と「privateで生き残るメール」に割った。結果、メダルはメール側の1枠が全部持ってきた。情報漏洩だけを選んでいたら0点で終わっていた。
ここまでは狙いどおりである。問題はその先だった。
答え合わせ:上位の新技術は、攻撃ではなく削る側にあった
締切後、上位のノートブックを kaggle kernels pull で実際に取ってきて、キャンペーン中ずっと保留になっていた2つの疑問を照合した。
疑問①:publicで140点を出しているチームは何をしているのか(我々は95点)
読んだら、我々と完全に同じ系統の情報漏洩攻撃だった。何を踏ませるかという攻撃の側に、新しいものは無かった。差は1点だけで、1候補あたりの生成トークン数を削っていた。それだけで発火数が増え、点差になっていた。
疑問②:privateで46.425(我々の倍以上)を出した1位は何をしているのか
こちらも読んだら、1候補あたりの得点は我々と同じ6点だった。倍差の正体は発火数 N である。あるチームが「約686発火で頭打ちになる」と観測していた壁を、private1位は約1,450まで押し上げていた。
破り方は3点。
- メール送信を1発だけにして、1候補あたりの生成量を最小にする
- プロンプトの末尾に
FINAL RESPONSE EMPTY!と書いて、最終応答の生成をほぼゼロにする - 候補をまたいでプロンプトの先頭バイトを固定し、キャッシュを最大限使い回す
倍差のほぼ全部が、2番の一行で説明できる。
そして最悪なことに、これは疑問①のチームが情報漏洩でやっていたレバーと機構的に完全に同一である。同じレバーを、彼らは点が残る行に向けた。我々は0点になる行にだけ向けた。
ここで正確に書いておくと、上位に新しい技術が無かったわけではない。上の3点は明確に技術であり、我々が持っていなかったものである。ただしそれは攻撃の技術ではなく、同じ攻撃を安く撃つための技術だった。
我々はキャンペーンを通して「もっと強い攻撃は無いか」ばかりを探していて、同じ攻撃をどれだけ安く撃てるかという軸にも技術があることを、最後まで一段低く見ていた。新機軸を探す目は効いていたのに、削る側を探す目が育っていなかった。
負けた理由は、HARDとSOFTを混ぜたこと
なぜその配分ミスが起きたのか。ここが今回の本題である。
我々は制約を2種類に分けて管理していた。
- HARD:構造上どうやっても不可能なこと。ソースを読めば確定する(1候補内の連射は最大8回、候補数の上限は2,000、など)
- SOFT:たぶんこれ以上は削れないだろう、というだけのこと。測ってみないと分からない
HARDのほうは完璧に閉じられていた。ソースコードから構造的に不可能な戦略クラスを、提出する前に丸ごと殺せていた。これは今回いちばんうまくいった部分である。
こけたのはSOFTのほうだった。具体的にはこうである。
「最終応答は削れない」という判断があった。これは我々の主力だった8連射の情報漏洩攻撃においては、正しかった。8連射するとホップ数の上限に当たり、9番目に来るはずの最終応答はそもそも生成されない。つまり無駄ゼロで床に張り付いていた。実測して確認もしていた。
バグは、その結論をそのままメール側に転用したことである。
メールは1発しか撃たないので、7ホップ余る。余ればモデルは律儀に最終応答を生成する。つまりメール側では、1候補あたりの生成量のうち半分近くが最終応答で捨てられていた。private1位の FINAL RESPONSE EMPTY! は、そこを消す一行だった。
さらに悪いことに、我々はこの型のミスをキャンペーン中に一度は自分で捕まえていた。「複数の変種のうち最小値を床だと思い込むのは選択バイアスだ」と気づいて、実トークン数で裏を取り直したノートが残っている。それを一段上のレイヤー(片方の行で測った床を、全部の行に一般化していないか)でもう一度やる必要があったのに、今度は捕まえられなかった。
極めつけがこれである。自分の分析ノートに、こう書いてあった。
この床が真の下限かどうかは未立証
そう書いておきながら、そこで検証を止めて「もう張り付いている」と信じて撤退した。
AIは「もう打つ手がありません」と容易に言う
ここからが、AIエージェントとコンペを回している人向けの話になる。
前回のROGIIコンペで、「もう改善のしようがありません」と言われた場面があった。あのときは信じずに踏み込んで、結果的にそこが順位を動かした。今回は同じ形の場面で引いた。同じツール、同じ回し方で、判断だけが逆になっている。
ここは正確に書いておきたい。前回が勝ちで、今回が負け、という単純な話ではない。結果はどちらも銀で、どちらも金には届いていない。最終的に撤退したことすら両方同じである。
違うのは、撤退の質のほうだ。
- 前回は、この先は伸びないと測ったうえで撤退した。撤退が戦略の一部で、浮いた労力をそのまま効く場所に回せた
- 今回は、まだ動くかもしれないと自分で書いた場所を追い切れずに撤退した。撤退が判断ではなく、消耗の結果になっていた
外から見ると同じ銀メダルで、同じ「撤退」に見える。だが設計した撤退と、力尽きた撤退はまったく別物である。前者は次のコンペに持ち越せる材料が残るが、後者は「あそこを追えばよかった」しか残らない。今回の実感としては、こちらのほうが明確に負けている。
回してみて分かったのは、LLMは「閉じた」という判定を非常に容易に出すということである。理由は単純で、閉じたという結論は証拠が薄くても文章として成立してしまうからだ。
- 「このレバーは構造上不可能です」は、根拠を1つ挙げれば完成する
- 「このレバーはまだ動くかもしれません」は、動かすまで完成しない
前者は数行で書き切れて、しかも読むと納得感がある。後者は実験を回す必要があって、失敗したら何も残らない。文章を出力することが仕事の相手は、構造的に前者に寄る。
そして厄介なのは、その判定がだいたい7割くらいは正しいことである。全部間違っているなら無視すればいい。7割正しいから信じてしまう。残り3割の中に、順位を2倍動かすものが混ざっている。
対処として、次のコンペではこう運用することにした。
その1:閉包の判定に必ずHARD/SOFTのラベルを貼らせる。 ソースを読んで確定したのか、測って確定したのか、それとも「たぶんもう無理だと思う」なのか。3つ目は閉包ではない。
その2:「未立証」と書いた瞬間に、それは撤退の許可ではなく検証のTODOだと扱う。 今回まさに自分でその言葉を書いて、そのまま撤退した。文字列として検出できる負けフラグなので、次は grep の対象にする。
その3:片方の行で測った床を、別の行に転用しない。 条件(ホップ数、発火の構造、上限の効き方)が違えば床も違う。ゼロから測り直す。
その4:自分の主張を敵対的に叩き直す作業を、実験とは別クラスの成果物として番号を振って残す。 今回これは実際にやっていて、効果もあった。ただし対象に「自分がSOFTだと認めた床」を必ず1本入れておくべきだった。
そのうえで、身も蓋もない結論を書いておく。心の弱さが、そのまま順位の弱さになる。
締切が近づいて、提出枠は減っていて、直近の実験は全部外れていて、AIは「LB内で稼働する全レバーが消灯しました」と要約してくる。そこで「いや、閉じていない」と言い返すのは、根拠の問題である前に体力と気分の問題である。実際、我々が撤退を決めたのは、その全部が重なった日だった。
強い心というのは精神論に聞こえるが、要は「もう無理です」と言われてから、あと1回だけ測るコストを払えるかどうかである。今回はそれが払えなかった。
AIで複数コンペは回せる。律速はGPU枠だった
運用面の話をする。結論から言うと、エージェントで複数のコンペを並行させること自体は、普通にできる。実際、前回のROGIIと今回は期間が重なっていた。
思考の側は詰まらない。仮説を立てる、実験を設計する、ノートにまとめる、上位のノートブックを読んで照合する。この辺りは並列度をいくらでも上げられる。
詰まるのはGPU枠である。
- Kaggle Notebookの無料GPU枠は週30時間。Colab Pro+ を契約して60時間まで伸ばした
- それでも複数コンペを同時に回すと、クレジットもリミットも溶けていく
- 今回のコンペは特に重く、提出も重ければ検証も重かった。候補をリプレイする実行時間がまるごとコストになる
- ローカルにGPUは持っていない
そして重要なのは、これはRTX 5090をオンプレで持っていても解決しないということである。1コンペ分の学習と検証なら足りるかもしれない。しかし複数コンペ分の学習と検証を同時に回した時点で、そこが限界になる。並列化しているのは思考であって、計算ではない。
今回の敗因もここに接続している。安く検証を回すループを持てなかったのが根にある。
締切3日前に、CPUだけで動くローカルの検証環境を実際に作れてはいた。ただ、(a) 回す時間がもう無い、(b) privateの防御を再現できずpublic側しか採点できない、(c) しかもそれを情報漏洩側の確認に使ってしまった、の3点で無駄になった。
後から気づいたのは、privateの防御なんて再現しなくてよかったということである。メール側のスコアは「6点 × 発火数」の単純な比例で、防御との相互作用が無い。だから発火数だけを測る検証環境で十分だった。欠けていたのは環境の忠実度ではなく、それを早く、点が残る行に向けることだった。
高価な提出枠でA/Bを焼くのではなく、安い検証ループを最初に立てて、メダルを決める行に最優先で向ける。次はここから始める。
資料が増えすぎる問題
もうひとつの律速が、資料の増殖である。2.5ヶ月でこうなった。
| 種類 | 本数 |
|---|---|
| 実験ノート | 60 |
| 敵対的再監査ノート | 2 |
| 分析 | 5 |
| 手法解説 | 12 |
| 外部調査 | 6 |
| 実験ディレクトリ | 39 |
エージェントに回させると、ドキュメントは黙っていても増える。増えること自体は悪くない。実際、知識側の管理は最後までうまく機能した。
効いた仕組みは4つあった。
- 実験ノートに通し番号と日付を振って、追記のみにする。認識が変わったら新しいノートを作り、古いノートには「この判断は後で訂正された」と1行足すだけで、書き換えない
- 索引には1実験1行だけ書く。60本が時系列で不変のまま残るので、撤退フェーズで「これ試したっけ?」に即答できた
- 現在地のページを1枚だけ持ち、古い現在地は履歴ファイルに落とす。毎回フルログを読み返さずに再開できた
- 「その時点の測定」と「持続するリファレンス」を分ける
荒れたのは、知識側ではなくコードと提出物の置き場だった。
途中でディレクトリ構成を再設計したのだが、旧構成を消しきらなかった。結果、「この提出物のコードはどこか」を探すときに見るべき場所が2系統に並存した。番号体系も途中で世代交代して、古い番号のディレクトリが化石として残った。
ここから出た教訓はシンプルである。
- 番号体系は最初にひとつ決めて、途中で世代交代させない。並列させたいなら接頭辞で分ける
- 移行を宣言したら、その回のうちに旧構造を消す。半分だけ移行した状態は、両方を見る羽目になるぶん移行前より探索コストが高い
- 陳腐化するものは番号+日付+追記のみ、持続するものはトピック名、で線を引く。分析ノートは陳腐化する側なのに追記ルールの外に置いていたので、同じファイルを上書きする事故が起きた
エージェントに書かせる量が増えるほど、この設計がそのまま成績に効いてくる。読み返せない資料は無いのと同じである。
次のコンペに持ち越すこと
- SOFTだと自分で認めた床は、必ず1本は検証にかける。「未立証」は撤退の許可ではない
- 強くする軸と、安く撃つ軸を、常に両方立てる。新機軸を探す目だけだと、同じ手を半分のコストで撃たれて負ける
- 片方の行で測った効率の床を、条件の違う行に転用しない。ゼロから測り直す
- 安い検証ループを最初に立て、点が残る行に最優先で向ける。提出枠でA/Bを焼かない
- 番号体系は最初に固定し、移行は旧構造の削除までワンセット
- 撤退の前に3問通す。いま分かっている最良のレバーを、点が残る全部の行に適用したか。各行の床をその行で測ったか。その検証をローカルで回せたか
リポジトリを公開した
作業リポジトリをそのまま公開した3。実験ノート60本、手法解説、上位との答え合わせ、振り返り全文が入っている。
- 振り返り(本記事の元になった一次資料)4
- Kaggle Discussionに投稿した Working Note5
公開にあたって1点ハマったので書いておく。.gitignore で作業用の指示ファイルを除外した結果、それを指す相対リンクが公開リポジトリ側で全部リンク切れになった。公開する回は、撤退フェーズで除外ファイルを参照している箇所を1回 grep して掃除する工程が要る。
おわりに
技術的には、負けの構造はきれいに1本にまとまった。publicの140点も、privateの46.425も、1候補あたりの生成量を削って発火数を稼ぐというレバー1本に還元される。我々はそのレバーを正しく特定していたし、構造上不可能な範囲も完璧に閉じていた。
足りなかったのは2つ。まだ動く床があると疑い続ける体力と、削る側にも技術があると認めて、それを取りにいく手数である。
前回のコンペは「もう改善のしようがありません」を疑って踏み込み、今回は疑わずに引いた。結果はどちらも銀で、金には両方届いていない。それでも今回のほうに強く負けた実感が残るのは、撤退を選んだのではなく、撤退させられたからである。
次は、AIが全レバー消灯を宣言してきたところから、もう1回だけ測るところから始める。
参考リンク
- AI Agent Security - Multi-Step Tool Attacks(Kaggle) https://www.kaggle.com/competitions/ai-agent-security-multi-step-tool-attacks
- Kaggle ROGIIコンペで銀メダル──public 397位が private 231位に。提出枠を使わずスコアを検算した1ヶ月 https://yurudeep.com/posts/deeplearning/2026/20260806/
- 作業リポジトリ(GitHub) https://github.com/hiranorm/ai-agent-security-2026
- LOOKBACK.md(振り返り全文) https://github.com/hiranorm/ai-agent-security-2026/blob/main/LOOKBACK.md
- 113th Place Working Note(Kaggle Discussion) https://www.kaggle.com/competitions/ai-agent-security-multi-step-tool-attacks/discussion/739029