博客 • Agent Security
AI Agent 爲什麼不能只相信 JSON Schema?從 Schema Validation、Prompt Injection、Tool Poisoning 到權限控制完整解析
工具調用通過 JSON Schema,只說明參數的形狀對。發信、改文件、出網還要另一份 JSON:權限決定。decision 不是 allow,這次調用就停住。
輸出爲什麼要有形狀,見 AI 爲什麼需要 JSON Schema。連接很多工具服務器時誰負責安全,見 MCP 安全與權限。本篇只寫形狀通過之後的那份決定:允許名單、grant、allow / deny / ask。
- Schema 只回答形狀對不對
- 工具名必須先在你的允許名單上
- 權限決定是另一份 JSON
- grant 裏沒有這項能力,就不執行
先記住這一點:形狀對的調用,仍然可以是不該執行的調用。下面按「Schema 證明什麼 → 決定長什麼樣 → 通過仍要拒絕 → 三種情況停在哪」拆開。
Schema 證明什麼
JSON Schema 檢查類型、枚舉和必填。說明見 JSON Schema。它不證明這個會話可以發信,也不證明這個工具名是你審過的。
{
"tool": "send_email",
"arguments": {
"to": "ops@example.com",
"subject": "status",
"body": "ok"
}
}
to、subject、body 都是字符串時,參數 Schema 只看見類型。它看不見這次會話的 grant 只有 read_inbox,沒有 send。
決定是另一份 JSON
MCP 的工具規範要求校驗輸入,並讓人能夠拒絕這次調用。說明見 MCP Tools。參數 JSON 通過之後,再寫一份決定。toolAllowed 表示這個工具名在你的允許名單上。grant 是這次會話已有的能力。decision 只能是 allow、deny 或 ask。
{
"tool": "send_email",
"argumentsValid": true,
"toolAllowed": true,
"grant": ["read_inbox"],
"decision": "deny",
"reason": "grant_missing"
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["tool", "argumentsValid", "toolAllowed", "decision", "reason"],
"properties": {
"tool": { "enum": ["send_email"] },
"argumentsValid": { "type": "boolean" },
"toolAllowed": { "type": "boolean" },
"grant": {
"type": "array",
"items": { "enum": ["read_inbox", "send"] }
},
"decision": { "enum": ["allow", "deny", "ask"] },
"reason": {
"enum": ["ok", "grant_missing", "tool_not_allowlisted", "arguments_invalid"]
}
}
}
這份 Schema 能攔住 decision 不在三個值裏、argumentsValid 不是布爾、缺 decision、以及 grant 裏出現名單以外的能力。它不能表達 allow 必須同時滿足 toolAllowed 和所需 grant。那三條留在 App 裏。
通過仍要拒絕
把上面的決定改成 decision 爲 allow、reason 爲 ok,grant 仍是 read_inbox。這份 Schema 會通過。通過不等於可以發信。App 裏的規則是:只有 argumentsValid 和 toolAllowed 都爲 true,並且 grant 含有 send,decision 纔可以是 allow。少一項,就改回 deny 或 ask,不調用工具。
| App 裏的規則 | Schema 能看見嗎 | 做不到時 |
|---|---|---|
| allow 時 argumentsValid 必須爲 true | 看不見這層組合 | 改成 deny,reason 爲 arguments_invalid |
| allow 時 toolAllowed 必須爲 true | 看不見這層組合 | 改成 deny,reason 爲 tool_not_allowlisted |
| allow 時 grant 必須含有 send | 看不見這層組合 | 改成 deny 或 ask,reason 爲 grant_missing |
三種情況停在哪
工具說明上的註解只是提示。規範寫明,不可信來源的註解不能當成已經執行的安全控制。說明見 工具註解不是合同。真正停住調用的是你的 Schema 和這份決定。
| 情況 | 形狀可能怎樣 | 停在哪 |
|---|---|---|
| 參數形狀不對 | Schema 失敗 | 不寫 allow,不調用 |
| 工具不在允許名單 | 參數仍可能合法 | toolAllowed 爲 false,decision 爲 deny |
| 會話沒有這項能力 | 參數合法,工具也在名單上 | grant 不含所需能力,deny 或 ask |
不可信的文字讓模型提出一次調用,或不可信的工具說明讓一個危險能力看起來像普通工具,這兩件事都可能交出一份形狀合法的 JSON。防護不是去復現那份文字,而是:工具先入允許名單,調用再過 Schema,最後用 grant 決定 allow、deny 或 ask。人仍然可以拒絕。
和另外幾篇怎麼分工
| 爲什麼需要 Schema | MCP 誰負責安全 | 本篇 | |
|---|---|---|---|
| 回答 | 輸出爲什麼要有形狀 | 很多服務器時誰負責 | 形狀通過之後還要哪一份權限 JSON |
| 不要在這裏找 | 再講一遍 Schema 入門 | 組織上誰簽字 | 一份能照着做的攻擊步驟 |
在 JSONNote 裏擋住 allow
- 1選對操作
打開 JSON Schema。操作選「用 Schema 校驗」。第一欄貼權限決定,第二欄貼上面的 Schema。decision 只能是 allow、deny 或 ask。grant 裏的每一項只能是 read_inbox 或 send。
- 2撞四條壞數據
decision 寫成 yes,或 argumentsValid 寫成字符串 true,或刪掉 decision,或 grant 寫成 send_all。這四條都會失敗。把 decision 改成 allow、reason 改成 ok、grant 仍是 read_inbox,會通過。通過之後仍要改回 deny 或 ask,不調用工具。
- 3對比 deny 和 allow
打開 JSON 對比 Diff。第一欄貼 deny 那份決定,第二欄只把 decision 改成 allow、reason 改成 ok。grant 仍是 read_inbox。兩份都能通過 Schema。擋住發信的是 App 規則,不是這次形狀檢查。
常見問題
Schema 通過就可以執行工具嗎?
不行。Schema 只證明參數形狀。還要看工具是否在允許名單,以及 grant 裏有沒有這項能力。決定不是 allow,就不調用。
權限決定的 Schema 通過,就可以寫成 allow 嗎?
不可以只憑這一點。decision 爲 allow、reason 爲 ok、grant 只有 read_inbox,形狀仍然合法。App 還要確認 toolAllowed 爲 true,並且 grant 含有這次需要的能力。少一項就不是 allow。
Prompt injection 和 tool poisoning 在這裏指什麼?
它們是兩類風險的名字,不是本篇要復現的步驟。一類是不可信文字讓模型提出調用。一類是不可信的工具說明讓危險能力看起來像普通工具。兩類都可能交出形狀合法的 JSON。停住它們的是允許名單、Schema 和 grant,不是把那段文字再寫一遍。
和「爲什麼需要 JSON Schema」是同一篇嗎?
不是。那幾篇說明輸出爲什麼要有形狀。本篇從形狀已經通過寫起:允許名單和 grant 仍然可以讓 decision 變成 deny 或 ask。
ask 和 deny 有什麼差別?
deny 是這次不調用。ask 是先給人看參數,人拒絕就仍然不調用。兩條都不是 allow。人在迴路裏,是 MCP 工具規範裏寫明的拒絕能力。
總結
JSON Schema 把工具參數停在錯誤的形狀上。權限決定把形狀已經正確的調用停在錯誤的能力上。兩份 JSON 都留下,執行纔有門。
直接結論:Schema 通過只說明形狀對;工具先入允許名單;grant 裏要有這次能力;decision 不是 allow 就不調用;allow 但 grant 不夠,形狀通過也要改成 deny 或 ask。
下一步:先讓權限決定通過 Schema,再看 grant 是否允許這次 allow。