AI Agent セキュリティガイド:悪意ある JSON、プロンプトインジェクション、Tool Calling、構造化データの攻撃リスク

2026年9月14日時点:Agent がツールを呼び、外部 JSON を読むなら、攻撃面は「誤った一文」から「本物の呼び出し」へ移る。悪意ある JSON、間接インジェクション、権限過大なツール、緩い Schema の防御マップ。

結論から: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

実行経路を外から内へ締めよ。プロンプトから始めるな:

  1. 先に契約を書け。ツールごとに一つの JSON Schema:required を揃え、additionalProperties: false、識別子は pattern / enum。
  2. Host でもう一度検証せよ。「モデルはすでに Schema に従った」と信じるな。ajv または同等;失敗は拒否。
  3. 射影し、素通しするな。allowlist フィールドだけ内部 DTO へコピーし、それから業務 API を呼べ。
  4. ツールを最小化せよ。fetch_url より fetch_public_doc;書き込みより読み取り専用。
  5. 認可はユーザーに付け、モデルに付けるな。ツール実装の中でセッション、テナント、資源 ACL を見よ。
  6. 外向きの副作用に門を置け。送信、送金、削除、本番デプロイ:ポリシーエンジンか人手確認。
  7. 外部テキストを格下げせよ。ツール結果、ページ、メールは system ではない。必要なら読み取り専用 Subagent が要約だけ親へ返す。
  8. 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、「誰が実行してよいか」は変えてはならない。