博客 • 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 证明什么 → 决定长什么样 → 通过仍要拒绝 → 三种情况停在哪」拆开。

Schema 证明什么

JSON Schema 检查类型、枚举和必填。说明见 JSON Schema。它不证明这个会话可以发信,也不证明这个工具名是你审过的。

三个字段都是字符串。这样的调用可以通过参数 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。

参数形状合法,工具也在名单上,但 grant 只有读信。decision 是 deny。
{
  "tool": "send_email",
  "argumentsValid": true,
  "toolAllowed": true,
  "grant": ["read_inbox"],
  "decision": "deny",
  "reason": "grant_missing"
}
第一栏贴决定,第二栏贴这份 Schema。操作改成用 Schema 校验。
{
  "$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。人仍然可以拒绝。

和另外几篇怎么分工

为什么需要 SchemaMCP 谁负责安全本篇
回答输出为什么要有形状很多服务器时谁负责形状通过之后还要哪一份权限 JSON
不要在这里找再讲一遍 Schema 入门组织上谁签字一份能照着做的攻击步骤

在 JSONNote 里挡住 allow

  1. 1
    选对操作

    打开 JSON Schema。操作选「用 Schema 校验」。第一栏贴权限决定,第二栏贴上面的 Schema。decision 只能是 allow、deny 或 ask。grant 里的每一项只能是 read_inbox 或 send。

  2. 2
    撞四条坏数据

    decision 写成 yes,或 argumentsValid 写成字符串 true,或删掉 decision,或 grant 写成 send_all。这四条都会失败。把 decision 改成 allow、reason 改成 ok、grant 仍是 read_inbox,会通过。通过之后仍要改回 deny 或 ask,不调用工具。

  3. 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。

返回博客