博客 • 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。