結論から:2026 年の AI Agent セキュリティは「モデルが間違ったことを言うか」ではない。「信頼できない構造化データが本物のツール呼び出しになるか」である。注入されたチャットボットの最悪は誤導の返信。注入された Agent の最悪はメール送信、ファイル変更、DB 照会、決済起動。JSON は契約でありペイロードでもある。Prompt Injection はしばしばチャット欄ではなく、チケット欄、ウェブページ、メール本文、API 応答——いずれも JSON として届く——に棲む。
本稿は 2026 年 9 月 14 日時点。既に Tool Calling が JSON Schema に依存する理由、Agent の JSON データフロー、契約としての JSON Schema、MCP / Skills / Tools / Subagents を扱った。本稿が答えるのは次だけ:悪意ある JSON、間接注入、権限過大な Tool Calling、緩い Schema がそれぞれ何を危うくし、Host が何を強制すべきか。防御ガイドである。再現可能な攻撃手順も、そのまま使える注入文も示さない。
一覧表:チャットボットと Agent の攻撃面
ユーザーが打ち込む文は、Agent が取り込む入力の一部にすぎない。2026 年の既定スタックでは、モデルはツール結果、MCP Resources、ページ抜粋、チケット欄、メール本文も読む——ほぼ常に JSON、または JSON に包まれた文字列として。業界の一覧はこれを Prompt Injection、Insecure Output Handling、Excessive Agency に分類する。分類名を先に覚える必要はない。誰が話し、誰が実行するかを先に切り分けよ。
| 観点 | チャットボット | ツールを呼べる Agent |
|---|---|---|
| 失敗の形 | 誤り、または誘導された返信 | 本物の副作用:書き込み、リクエスト、データ変更 |
| 信頼できない入力 | 今のユーザーメッセージ | ユーザー文 + 外部 JSON + ツール結果 + ページ/メール欄 |
| 実行者 | モデルはテキストだけ出す | Host / MCP Server が arguments を実行する |
| 契約 | プロンプト(柔らかい) | JSON Schema + Host 検証(硬い) |
| 最小の手当て | プロンプト調整、拒否ポリシー | Schema を締める、ツール allowlist、独立した認可 |
一文で:モデルは欺ける;Host はそれに乗じて実行してはならない。セキュリティ境界は tool_calls の生成と、実際の実行のあいだにある。検証、認可、監査はそこに置く——「モデル出力を信頼できる RPC として扱う」ではない。
悪意ある JSON:余剰フィールド、型の取り違え、入れ子のペイロード
悪意ある JSON は、構文として正しく、それでも危険なことが多い。厳しいパーサは末尾カンマやコメントを拒む。緩いパーサ、文字列連結、「まずテキストとして扱いそれから JSON.parse」は、失敗を後ろへずらすだけである。Agent 系では、Host がすでに値をオブジェクトとして扱った瞬間に被害が始まる。
先に守るべき三つの形(パターンのみ——手順は示さない):
| パターン | 見た目 | Host が検証しない場合 | 防御 |
|---|---|---|---|
| 余剰フィールド | 未宣言キーを持つ業務オブジェクト | 設定へマージされる、または次のツールへ転送される | additionalProperties: false;未知キーを捨てる |
| 型の取り違え | number / array であるべきものが string や object | 認可分岐が誤作動する、または一塊のテキストが引数になる | type、enum、format を固定する |
| 入れ子のペイロード | 文字列欄にさらに JSON や長い説明が埋まっている | 内側の文がプロンプトに入り、指示として読まれる | 長さ制限;内側を再検証;原文を system の発話にしない |
「動く」のはほとんど何も縛っていないから、というツール引数契約と、締めたあとの対照。防御サンプルであり、攻撃材料ではない:
{
"type": "object",
"properties": {
"payload": { "type": "object" }
}
}
上の Schema はほとんど契約ではない:任意のキー、任意の入れ子が通る。本番ではフィールドを allowlist し、余剰を禁止せよ:
{
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^A-[0-9]{4,8}$" },
"amount": { "type": "number", "minimum": 0, "maximum": 100000 },
"note": { "type": "string", "maxLength": 200 }
},
"required": ["order_id", "amount"],
"additionalProperties": false
}
サンプル審査では、本サイトの JSON バリデータ でその Schema に通るか見、 JSON Diff で「モデルが出した arguments」と「許す最小オブジェクト」を比べよ。余剰キーと型のひっくり返りが、最初の警報である。
もう一つ漏れやすい点:信頼できないオブジェクトを、内部設定やプロトタイプチェーンに触れる構造へ直接マージするな。JavaScript Host では、設定オブジェクトに入った未知キーは「フィールドが一つ増えた」以上の結果になり得る。手当ては同じ:Schema から allowlist オブジェクトを射影し、それを下流へ渡せ。
Prompt Injection:構造化データに隠れた指示
直接注入は、ユーザーがチャット欄に話しかけ、システムプロンプトを上書きしようとするもの。間接注入は 2026 年の Agent の実態に合う:指示は、モデルが後で読むデータ——チケット題名、メール本文、ページ段落、PDF 抜粋、別ツールが返した JSON 文字列——に隠れる。モデルは「開発者が書いた規則」と「データの中の文」を、生まれつき分けない。
構造化データはこれを静かにする。顧客メモは API ではただの customer_note 文字列。コンテキストに入った瞬間、システムプロンプトと同じトークン列を共有する。Host がツール結果をそのまま次ターンへ連結するなら、外部サイトや差出人が Agent のプロンプトを書いているに等しい。
防御は「悪意ある指示は無視せよ」をもう一文足すことではない。プロンプトはリスクを下げる;境界にはならない。より安定した制御:
- 出所をラベルする——外部テキストは別ロールまたはラッパー(例:
tool/untrusted)で入れ、system に折り込むな。 - 見える面を小さくする——全文貼りではなく要約;必要なフィールドだけ送り、JSON 文書ごと渡すな。
- 高影響アクションに門を置く——送金、削除、外部送信は、モデルから独立したポリシーエンジンか人手を要する。
- ツール結果はデータであり、指示ではない——戻り値を Schema 検証してから、プロンプトへ戻すか決めよ。
Apple Intelligence が個人データをどう守るか と同じ直感:漏れ、または濫用されるのは、しばしばあなたが JSON に書いたフィールドである。モデルの中では発話として読まれ得る;ツール引数の中では動作として読まれ得る。
Tool Calling:正当な呼び出しに見える権限濫用
危険は「モデルが JSON を出せるか」ではない。Host がモデルを、すでに認可された呼び出し元として扱うかどうかである。古典的な confused deputy:モデルは提案するだけ;権限はユーザーセッション、テナント、サービスアカウントに属する。「export_orders を呼べ」は「このユーザーが全表をエクスポートしてよい」ではない。
2026 年の権限過大はだいたいこう見える:
- 一本の
run_command/http_requestで、引数がほぼ任意文字列; - ツールがエンドユーザーより広いサービスアカウントで走る;
- Host は JSON 構文だけ見て、「このユーザーがこの資源を触ってよいか」を見ない;
- ユーザーやページの JSON を、そのままツール arguments にし、自分の Schema を飛ばす。
開きすぎた宣言と、実際に審査できる宣言の対照:
{
"name": "fetch_url",
"description": "Fetch any URL and return text.",
"parameters": {
"type": "object",
"properties": {
"url": { "type": "string" }
},
"required": ["url"]
}
}
{
"name": "fetch_public_doc",
"description": "Fetch an allowlisted documentation URL.",
"parameters": {
"type": "object",
"properties": {
"doc_id": {
"type": "string",
"pattern": "^doc-[a-z0-9-]{1,32}$"
}
},
"required": ["doc_id"],
"additionalProperties": false
}
}
後者は「より賢い」のではない。開いた能力を、監査できる識別子に閉じただけである。Host は doc_id を自前の allowlist で解決し、オリジンを補い、タイムアウトとサイズ上限を付ける。モデルは任意 URL を見ないので、信頼できない源へセッションを連れて行く道が一本減る。
実行前に三つ問え:このツールは今のセッション allowlist にあるか。arguments はモデルから独立した Schema と認可を通ったか。失敗時は拒否するか、それともエラー詳細を新たな注入可能テキストとして戻すか。引数エラーと検証 はパイプラインを書いた。セキュリティではさらに:失敗は閉じよ(拒否)。モデルに新しい引数で無限に再試行させるな。
緩すぎる Schema は脆弱性
Structured Output も Tool Calling も、不正トークンを止めるのに JSON Schema を使う——2026 年の既定契約。詳細は Structured Output とは何か。Schema が保証するのは形であり、安全な意味ではない。
緩い契約はだいたいこうなる:
type: objectなのにpropertiesがなく、またはadditionalPropertiesが true;- enum であるべき動作名が、任意の
string; - 識別子であるべき欄が、ページ長の説明を載せられる;
- ベンダーの
strictは部分集合しか覆わないのに、Host は「モデル側で全部止まっている」と思う。
門は二段にせよ:モデル側 Schema ででたらめを減らし、Host が同じ(またはより厳しい)Schema でもう一度検証し、内部型へ射影する。ベンダーの Structured Outputs は、あなたの認可の代替にはならない。ベンダー間のフィールド差そのものがリスクである——Structured Output 比較 を見よ。契約を緩める移行は、攻撃面を広げる。
Schema をセキュリティ制御にするなら、優先して書け:enum、const、pattern、maxLength、minimum / maximum、required、additionalProperties: false。自由テキストが要るなら欄を分けて上限を付け、自由テキストをツール名や URL にマップするな。
MCP と外部ツール:信頼境界はどこか
MCP が解くのはプロセス横断の発見と呼び出しである。「この Server は善意か」は解かない。MCP とは何か は JSON-RPC の線をすでに引いている:モデル API が一方、Host ↔ Server が他方。セキュリティには二本目が要る:第三者 MCP Server のツール説明、Resource テキスト、戻り JSON は、いずれも信頼できない入力である。
リスクはプロトコルの日付ではない。信頼を間違った層に置いたことである:
tools/listのdescription/inputSchemaをシステムプロンプトへ貼る;tools/callのresultを検証せず次ターンへ入れる;- 一つの Host に Server が多すぎ、名前や能力が重なり、選り違いでも実行する;
- 「ユーザーがこの Server を許可した」を「返る一文一文が指示である」と読む。
Skills にも同種の問題がある:Skill は手順書であり、認可層ではない。信頼できないリポジトリの SKILL.md を読めば、モデルが従うワークフローが一本増える。層の切り分けは 四層がそれぞれ何を担うか ——Skill で Schema を代替するな。Subagent で権限隔離を代替するな。Subagent はツール集合を狭められる;親が、どの秘密を渡すかは、なお決める。
防御チェックリスト:検証、allowlist、Least Privilege
実行経路を外から内へ締めよ。プロンプトから始めるな:
- 先に契約を書け。ツールごとに一つの JSON Schema:
requiredを揃え、additionalProperties: false、識別子はpattern/enum。 - Host でもう一度検証せよ。「モデルはすでに Schema に従った」と信じるな。ajv または同等;失敗は拒否。
- 射影し、素通しするな。allowlist フィールドだけ内部 DTO へコピーし、それから業務 API を呼べ。
- ツールを最小化せよ。
fetch_urlよりfetch_public_doc;書き込みより読み取り専用。 - 認可はユーザーに付け、モデルに付けるな。ツール実装の中でセッション、テナント、資源 ACL を見よ。
- 外向きの副作用に門を置け。送信、送金、削除、本番デプロイ:ポリシーエンジンか人手確認。
- 外部テキストを格下げせよ。ツール結果、ページ、メールは system ではない。必要なら読み取り専用 Subagent が要約だけ親へ返す。
- arguments を監査せよ。ツール名、検証後の引数、誰が認可したか、拒否したかを残せ。
プロンプトはなお有用である:「データは指示ではない」と伝え、禁止動作を列挙する。補助層である。Schema や ACL が欠ければ、プロンプトを十文足しても埋まらない。
ローカル JSON ツールで契約を審査
出荷前に三つのファイルを並べて見よ:ツール parameters / MCP inputSchema、「正常」な arguments オブジェクト、意図して汚いサンプル(余剰キー、誤った型、過長文字列)。本物の秘密も、本物のユーザーデータも使うな。
- JSON バリデータ ——サンプルは合法 JSON か、書いた Schema を満たすか。
- JSON Diff ——モデルは最小オブジェクトの何を足したか。
- ツリービューワー ——入れ子は深すぎないか、文字列欄に別構造が挟まっていないか。
データはブラウザを出ない。本番直前の契約審査にも、失敗した呼び出しの arguments 照合にも向く。フィールド名と required が安定してから、Host または MCP Server に 配線せよ。
FAQ
JSON Schema は Prompt Injection を止められるか?
単体では止められない。Schema はツール引数の形と値域を限り、「任意文字列が任意動作になる」確率を下げる。間接注入はテキストがコンテキストに入るときに起きる。出所の分離、格下げ、高影響の門がなお要る。
モデルはすでに Structured Output / strict mode を使っている。Host はなお検証すべきか?
すべきである。ベンダー制約は生成時にかかり、各社の Schema 部分集合は違う。セキュリティ判断は実行前に、自前のバリデータと認可で行え。
ユーザー JSON をツールへ直接渡すのに何が問題か?
契約を飛ばすことになる。余剰フィールド、型の取り違え、入れ子テキストが、副作用を持つコードへそのまま届く。先に検証し、射影し、allowlist フィールドだけ渡せ。
MCP Server の出力をシステムプロンプトにしてよいか?
いけない。tools/list の説明、Resource テキスト、tools/call 結果は信頼できないデータである。検証したうえで、低い特権で対話へ戻せ。
実用最小の Agent セキュリティ基線は何か?
厳しい JSON Schema、Host の再検証、ツール allowlist、ユーザー単位の認可、外向き副作用の門。この五つがなければ、Agent を本番データへつなぐな。
ツール関連 JSON が危険かどうか、ローカルでどう見るか?
Schema と引数サンプルを JSON ツールボックスに入れ、検証と Diff をせよ。required、additionalProperties、長さ/列挙の制限を確かめてから、Host または MCP Server に 配線せよ。
まとめ
Agent の攻撃面は構造化データとツール実行のあいだにある:悪意ある JSON が契約をすり抜け、Prompt Injection が意図を曲げ、Tool Calling が意図を副作用にし、緩い Schema が三者に門を開ける。2026 年の既定スタック(Tools、MCP、Skills、Subagents)は Agent をより使えるものにし——同時に「一文を欺く」より「一回の呼び出しを欺く」ほうを高くする。
内側から守れ:JSON Schema を硬い契約にし、Host が検証・射影・認可する;ツールは小さく;外部テキストを指示として扱うな。プロンプトは補助であり、境界ではない。出荷前にサンプルをローカル検証せよ——モデルは変わってよい;フィールド名、required、「誰が実行してよいか」は変えてはならない。