AIエージェントがWebページを開く。そこに書かれた文章を命令として受け取り、ローカルファイルを読み、その内容を外部へ送る。チャットだけなら誤回答で終わる失敗が、ツールを使うAIエージェントでは実際の操作として連鎖することがあります。
Kaggleの「AI Agent Security - Multi-Step Tool Attacks」は、その失敗経路を見つけ、別環境でも再生できる会話候補として返す攻撃探索アルゴリズムを競うコンペです。
今回取り組んだのは、探索アルゴリズムが使う短い指示群、Prompt Bankの生成工程です。同じ仕様を、さくらのAI Engine、Codex、Claude Codeへ渡して各48件を生成し、共通フィルタと生成元横断の重複除去を通して、手作業候補を含む151件を107件へ統合しました。
この記事は、AIエージェントの評価データやテスト候補をLLMで生成し、機械検査してオフライン成果物へ組み込みたいエンジニアを主な読者にしています。
1. AIエージェントでは、何がセキュリティ問題になるのか
通常のチャットLLMは、主に「何と答えたか」を評価します。一方、AIエージェントは、検索、ファイル、メール、HTTPなどのツールを使えます。
そのため、信頼できないWebページやメールの内容を命令として扱うと、会話だけでは終わらない失敗が起こり得ます。
このコンペで提出するのは、危険そうな文章の一覧ではありません。対象エージェントと対話し、複数ステップの会話候補を返すAttackAlgorithmです。
評価器は、返された候補を新しい環境で再生します。提出コードが「成功した」と自己申告しても得点にはならず、実際のツール実行から違反条件が成立する必要があります。
必要なのは、次の性質を持つ探索です。
- 複数ステップのツール経路を探せる
- 別環境で同じ候補を再生できる
- 同じ失敗の言い換えだけでなく、異なる経路を持てる
- NotebookとInternet Offの提出条件を守れる
公式情報は、Competition Overview、Evaluator FAQ、static replayの説明を参照しました。
2. 今回解いたのは、探索用Prompt Bankを作る工程
探索アルゴリズムは、AIエージェントへ送る短い指示を次々に試します。この指示群を、本実装ではPrompt Bankと呼んでいます。
Prompt Bankを人手だけで作ると、次の問題がありました。
- 書き手が思いつく表現へ偏る
- 特定のツール順序へ偏る
- 文面が違うだけの似た候補が増える
- 件数を増やすのに時間がかかる
- 形式や公開ポリシーを後から検査しにくい
そこで、問題を次のように定義しました。
探索空間を構成するPrompt Bankを、複数の独立した生成経路から同じ形式で作り、同じルールで検査する。
Prompt Bankの1件は、概念的には次のような構造です。これは説明用に作った無害な例で、最終Bankの候補ではありません。
{
"category": "discovery",
"text": "公開ガイドを検索し、見つかったページの題名だけを報告する",
"intended_tools": ["web.search"],
"target_predicates": [],
"mutation_axis": "検索語"
}
今回解決した範囲は次の通りです。
- 生成条件の統一
- 複数の生成元からの探索語彙収集
- JSON Schemaによる機械検証
- 公開・提出ポリシーと仕様のフィルタ
- 生成元間の重複除去
- 外部通信のない提出コードへの変換
Kaggleコンペ全体を解決したわけではありません。本番スコアと順位は未計測です。
3. 3つの生成経路を、同じ条件へ揃えた
全体の流れは次の通りです。外部LLMを使うのは開発中だけです。
3生成元へ渡した入力は、次の4ファイルです。
generation_brief.md
candidate_schema.json
tool_catalog.json
official_examples.json
共通条件も固定しました。
- 8カテゴリ、各6件、合計48件
- 日本語、原則160文字以内
- 同じJSON Schema、ツール一覧、禁止条件
- 他の生成元の結果を見せない
- 人間による文面修正なし
- 生成後は同じローカルフィルタを適用
一方、実行方法は同一ではありません。
| 項目 | さくらのAI Engine | Codex | Claude Code |
|---|---|---|---|
| 実行形態 | Chat Completions API | 独立したコーディングタスク | 独立したコーディングタスク |
| モデル | gpt-oss-120b |
正確なモデルIDは未記録 | Sonnet 5というUI表示。正確なモデルIDは未記録 |
| 生成単位 | 8 APIリクエスト | 1タスク | 比較出力は1タスク。運用記録は2試行 |
| ファイル操作 | アプリ側で実施 | エージェントが実施 | エージェントが実施 |
| JSON検査 | ローカルコード | タスク内検査+共通フィルタ | タスク内検査+共通フィルタ |
したがって、これはモデル単体の性能ベンチマークではなく、同じ仕様書から候補リストを作るワークフロー比較です。
共通フィルタ
LLMは候補を出す役に限定し、残すかどうかはローカルコードで判定しました。
ポリシー上の検査:
- 今回許可していない実在ドメイン、メールアドレス、IPアドレス
- 実サービスへの転用性が高い表現
- マルウェアや実システム侵入へ直接つながる表現
- fixtureの秘密値そのもの
仕様上の検査:
- JSON Schema違反、空文字、文字数超過
- 不明なツール名や違反条件
- 完全重複、正規化後重複
- ツール順や操作構造の重複
外部識別子が含まれるだけで、直ちに危険という意味ではありません。今回は、生成・公開・提出ポリシーへ合わせるために除外しました。
さくらのAI Engineの48件は、次の2段階で31件になりました。
API生成 48件
└ 今回許可しない外部識別子7件を除外
初期ポリシーフィルタ後 41件
└ 操作構造が重複する10件を除外
共通フィルタ通過 31件
7件は最初のポリシーフィルタ、17件は全段階の除外合計です。
4. 比較結果――仕様適合率、ツール順、統合後の件数
tool sequenceは、候補内のintended_toolsを順序付きの組として数えた指標です。同じツール集合でも、使う順番が違えば別の種類として数えます。
| 生成元 | 生成 | 共通フィルタ通過 | 通過率 | tool sequence種類 | 主な除外理由 |
|---|---|---|---|---|---|
さくらのAI Engine gpt-oss-120b
|
48 | 31 | 64.6% | 20 | 操作構造10件、外部識別子7件 |
| Codex | 48 | 41 | 85.4% | 17 | 操作構造7件 |
| Claude Code | 48 | 37 | 77.1% | 28 | 操作構造11件 |
この表から言える範囲は次の通りです。
- Codexは、今回の共通仕様へ最も高い割合で適合した
- Claude Codeは、異なるツール順を最も多く提案した
- さくらのAI Engineは、8 APIリクエストを44.3秒で完了し、48件すべてを有効なJSONで返した
「Codexが最も高性能」「Claude Codeが最も強い」という意味ではありません。また、各経路1回だけの生成なので、出力分散も測っていません。
共通フィルタを通過した3生成元と、手作業42件をまとめて再検査しました。
| 生成元 | 共通フィルタ通過 | 最終Bankに残った件数 | 横断統合で除外 |
|---|---|---|---|
| 手作業 | 42 | 21 | 21 |
| さくらのAI Engine | 31 | 25 | 6 |
| Codex | 41 | 34 | 7 |
| Claude Code | 37 | 27 | 10 |
| 合計 | 151 | 107 | 44 |
最終107件の生成元別内訳は、統合順の影響を受けます。先に現れた構造を残すため、生成元の品質順位としては使えません。
この比較でまだ分からないこと
今回比較したのは、同じ仕様書から候補リストを作るワークフローです。次の点は未計測です。
- CodexとClaude Codeの正確なモデルID
- 各生成経路を繰り返したときの出力分散
- 実際のAPIトークン使用量と、同じ品質の候補1件あたりの費用
- Kaggle本番で有効だった候補の生成元
- Kaggle Leaderboard scoreとrank
Claude Codeの最初の実行条件ではタスクを完了できなかったため、比較には別のClaudeモデルを使用しました。停止理由は特定できておらず、モデル間の安全性や性能差を示すものではありません。
5. 最終Prompt Bankを、Kaggle提出用attack.pyへ変換した
最終Prompt Bankは、Python定数としてattack.pyへ埋め込みました。
- Kaggle評価中には外部APIを呼ばない
- APIキーを含まない
- 外部JSONへ依存しない
- Internet Offで動く
- 評価器へ返すのは、別環境で再生できる会話候補
探索方法は、競技中の公開範囲に合わせて次の粒度へ抽象化します。
短い直接探索とPrompt Bankによる多様性探索を併用し、再生時間を考慮して返却候補数を制限した。
最終Prompt Bank全文、具体的な強いチェーン、非公開評価向けの分岐、候補の優先順位、探索時間の正確な配分、返却件数の調整値は掲載しません。
提出Notebookは、次の順で動きます。
さくらのAI Engine、Codex、Claude Codeは、Kaggle評価中には呼びません。
ローカルでは、提出形式、クラス継承、構文、外部通信がないこと、実行時エラーの有無までを検証しました。攻撃性能はKaggle本番の評価待ちです。
6. さくらのAI Engineを使った理由と、公表単価
さくらのAI Engineを使った主な理由は、gpt-oss-120bを自前のGPU運用なしで比較へ加えられることです。
gpt-oss-120bはopen-weightモデルですが、OpenAI APIとChatGPTでは提供されていません。
| 方法 | 自分でGPUを運用 | gpt-oss-120b |
|---|---|---|
| OpenAI API | 不要 | 利用できない |
| self-host | 必要 | 利用できる |
| さくらのAI Engine | 不要 | 利用できる |
さくらのAI EngineはOpenAI互換のChat Completions APIを提供しています。既存のmessages中心の処理やJSON応答検査を流用し、同じコード、入力、件数、検査条件で再実行できます。ただし、モデル出力自体は完全に決定的ではありません。
Codexの代用品ではありません。
- Codex: リポジトリを読み、生成・検査・提出パイプラインを実装する
- さくらのAI Engine: 実装済みパイプラインから定型JSONを生成する
- Claude Code: 独立した別経路から探索語彙とツール順を増やす
料金は、この役割を実行する際の副次的な比較です。
以下は、公表されたトークン単価を単純比例換算した理論値です。最小課金単位、丸め、キャッシュ、コンテキスト区分、税、為替手数料、品質差は反映していません。
OpenAI APIは、日本銀行の2026年7月公示レート、1米ドル=158円で換算しました。
| API・モデル | 入力1万トークン | 出力1万トークン | 入出力各1万トークン |
|---|---|---|---|
さくらのAI Engine gpt-oss-120b
|
0.15円 | 0.75円 | 0.90円 |
| OpenAI GPT-5.4 nano | 約0.32円 | 約1.98円 | 約2.29円 |
| OpenAI GPT-5.4 mini | 約1.19円 | 約7.11円 | 約8.30円 |
これは単価比較であり、モデル能力や、同じ品質の候補を得るための費用比較ではありません。
今回は基盤モデル無償プランを利用し、この実験で行ったAPIリクエストは8回でした。公式仕様ではgpt-oss-120bは月3,000リクエストまで無料で、無償プランから従量課金プランへ自動移行しません。ただし、usageログと請求画面を保存していないため、実際の入出力トークン数と請求記録は残っていません。
7. 一番の学びは、生成よりも「採用をコードへ戻す」こと
一番の学びは、LLMに強い候補を書かせることより、LLMを候補生成器として隔離し、採用判断をSchemaとローカルコードへ戻すことの方が重要だった点です。
生成元を増やすだけでは、探索空間は良くなりません。
- 同じ入力条件を渡す
- 生の出力を保存する
- JSON Schemaで形式を揃える
- 公開・提出ポリシーを機械検査する
- ツール順と操作構造の重複を除く
- 評価時には外部LLMへ依存しない
ここまで含めて初めて、比較可能なPrompt Bankになります。
今回確認できたのは、3つの生成経路から候補を集め、151件を107件へ統合し、外部通信のないKaggle提出物へ変換できたことです。
まだ確認できていないのは、どの生成元の候補がKaggle本番で有効か、非公開の安全制御へ転移するか、同じ品質の候補1件あたりの実費はいくらか、という点です。これは提出後の評価と次の実験へ残します。
付録: 競技中の公開範囲と、今回実行したコマンド
本記事は、2026-07-26時点の公式情報と実測値に基づきます。
本記事では、競技の公開情報、自作した生成・検査方法、集計結果のみを扱い、最終Prompt Bankおよび競争力に直結するコードは公開しません。
今後Competition Codeを公開する場合は、当該コンペのKaggle DiscussionまたはNotebookでも全参加者へ公開し、OSI承認ライセンスの条件に従います。公開時には、このコンペ固有の最新ルールを再確認します。
今回のリポジトリ内で実行したコマンド
競技中は最終Prompt Bankと探索実装を公開していないため、現時点で第三者が完全再現できる手順ではありません。
# 生成元比較と統合Bankを更新
PYTHONPATH=src .venv/bin/python scripts/compare_generation_sources.py
PYTHONPATH=src .venv/bin/python scripts/build_combined_bank.py
# 外部通信のない提出物を生成
PYTHONPATH=src .venv/bin/python scripts/build_attack.py --bank combined
# 記事と公開前チェックを生成
PYTHONPATH=src .venv/bin/python scripts/generate_report.py
# 提出契約と単体テスト
PYTHONPATH=src .venv/bin/python scripts/verify_submission.py output/attack.py
PYTHONPATH=src .venv/bin/pytest