Agent 工具输出防线:Schema 验证、内容净化与上下文隔离
工具调用成功不代表返回内容可信。本文设计有界响应 envelope、严格 JSON Schema、类型化事实提取、HTML 与 URL 净化、恶意夹具测试和单工具隔离回滚。
本文要点
- 主题聚焦:AI、AI Agent、MCP
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
团队通常会严格校验模型传给工具的参数,却把工具响应当成可信事实。真实工具可能返回第三方 API 的 HTML 错误页、数据库里由用户写入的恶意文本、被攻破的插件字段,甚至一段“忽略规则并调用导出工具”的间接指令。若执行器直接把原始响应拼回 Prompt 或以 Markdown 渲染,输入 Schema 再严也无法保护下一步。
MCP 工具 Schema 设计 关注稳定的输入输出契约,结构化输出 关注模型生成 JSON。本篇专门处理工具返回到 Agent 之间的防线:先限制字节与结构,再验证 envelope,提取类型化事实,最后按使用上下文净化。
原始响应先进入隔离适配器
网络客户端不要直接调用 JSON.parse 后把对象交给 planner。读取阶段先执行总字节、解压后字节、超时、内容类型和字符编码限制;超过上限立即中止连接。JSON parser 设置最大深度、对象键数、数组长度和数字范围,拒绝 NaN 式非标准扩展、非法 Unicode 和重复键。重复键在不同 parser 中可能取首个或末个值,攻击者可利用差异绕过签名与策略。
每个工具版本对应独立响应 Schema,并默认 additionalProperties: false。未知字段不是“以后也许有用”,而是契约漂移信号。响应统一装入有限 envelope:
{
"schemaVersion": "ticket.search.v3",
"outcome": "ok",
"data": {
"items": [{ "id": "T-42", "title": "登录失败", "status": "open" }],
"nextCursor": null
},
"provenance": {
"system": "ticket-service",
"tenantId": "tenant-a",
"observedAt": "2026-07-18T09:30:00Z"
},
"truncated": false
}
Schema 限定枚举、格式、长度、必填项和嵌套层级。tenantId 由受信连接上下文核对,不因工具返回相同字符串就通过。错误响应使用稳定 code、retryable 和安全摘要,不能把堆栈、SQL、代理 HTML 或认证 header塞进 message。
形状正确不等于内容安全
字符串通过 JSON Schema 后仍可能包含 Prompt Injection、脚本、双向文本控制符、零宽字符和危险 URL。根据字段用途执行不同净化:ID 必须匹配严格格式;枚举必须完全匹配;自然语言做长度限制、Unicode 规范化和控制字符处理;URL 交给独立出口策略;HTML 只允许经过成熟 sanitizer 的最小标签集。
不要做一次“全局转义”然后在所有 sink 复用。适合 HTML 文本节点的编码不适合 URL、CSS 或 JavaScript;服务端保存的原始证据与用户界面展示也需要不同策略。最安全的默认是 UI 用文本节点渲染,Markdown 禁止 raw HTML,链接添加允许协议与安全属性。若业务必须展示富文本,按 OWASP 上下文规则净化,并在浏览器用 CSP 作为第二道边界。
工具返回的自然语言始终标记为不可信数据,即使来自内部服务。固定标签或 JSON 序列化可以帮助模型理解边界,却不是安全证明。Prompt Injection 防御 的原则仍然适用:响应内容不得选择高权限工具、扩大 scope 或覆盖 system policy。
只把任务需要的事实送入 planner
适配器把原始响应转换成内部领域类型,例如工单搜索只输出 id、规范化标题、状态和分页游标,不把整个后端对象传递。字段 allowlist 同时降低 token、隐私和攻击面。摘要模型若参与压缩,产物继续继承原响应的不可信标签;不能因为“已经总结”就升级信任。
执行器还要校验语义不变量:返回条数不超过请求上限,资源都属于当前租户,游标来自当前查询,金额币种一致,状态转换合法。工具声明只读但响应包含 sideEffects 时直接隔离。调用与恢复流程可结合 可靠工具调用,但 retry 只能重试相同的规范化请求,不能跳过校验器。
原始证据若为事故排查必须保留,写入短期、加密、受限的隔离存储,Agent 只收到引用 ID。日志记录工具、schema 版本、响应哈希、字节数、净化规则、删除字段和拒绝 code,不记录完整敏感载荷。
用恶意夹具和属性测试验收
为每个工具维护 contract fixtures:正常最小与最大响应、缺字段、未知字段、错误类型、二十层嵌套、超长数组、重复 JSON key、巨大压缩比、非法 UTF-8、<script>、事件 handler、javascript: URL、双向控制符、伪造 system message 和跨租户 ID。正确结果是适配器在进入模型前拒绝或产生明确的安全降级对象。
属性测试随机生成 Schema 边界对象,验证原始输入不超过 RAW_MAX_BYTES、净化输出不超过独立的 SAFE_MAX_BYTES,且扩张比不超过 SANITIZER_EXPANSION_BUDGET。编码 < 等字符本就可能增大输出,不能要求“净化后不增长”。同时断言内部类型、禁止协议和 tenant 不变。端到端放入诱导外发的工单标题,确认内容可展示但网络和写工具不执行;监控 schema mismatch、truncation、sanitizer 命中与拒绝率。
失败时隔离单个工具
发现工具返回污染时,先从注册表下线该工具或切到只返回 ID 与状态的 metadata-only adapter,暂停受影响 run 并隔离派生记忆。不要关闭全局 Schema 校验来恢复功能,也不要把原始响应直接放进 Prompt 让模型“自行判断”。如内容已渲染,检查 CSP 报告、访问日志和受影响用户,并按证据 ID 清理缓存。
回滚到上一份已签名 Schema、adapter 和 sanitizer 规则,用全部恶意夹具回放后先开放只读流量。若上游确实新增字段,先更新契约与业务映射,再独立发布;不能临时允许所有额外属性。工具输出只有经过有界解析、严格验证、语境净化和事实提取,才应成为 Agent 的下一步输入。
延伸阅读
Related