安全的 MCP Server 不能只做到“工具 schema 校验通过”。模型输出、网页内容、工具返回值和远程服务器都可能是不可信输入;真正的边界是:谁能调用、能调用什么、对哪个资源调用、何时需要审批、执行是否可重复、输出是否泄密,以及事故后能否追溯和撤销。

先建立哪一种威胁模型?

把一条工具调用拆成六个主体:

  1. 用户:拥有明确目标与资源权限。
  2. Agent:根据上下文提出工具调用,但不是授权主体。
  3. MCP client:展示、审批并调度调用。
  4. MCP server:验证身份、权限、参数和资源范围。
  5. 下游系统:数据库、云 API、文件系统或消息平台。
  6. 不可信内容:网页、邮件、文档、日志与工具输出。

关键原则是“模型建议动作,确定性系统授权动作”。即使模型准确理解了用户意图,MCP server 仍必须根据当前身份和资源重新鉴权。ZoyTown 的 MC AI / AIBot也采用 LLM 规划、确定性状态机执行的边界;这类分层比把全部权限交给自然语言循环更容易验证。

远程 MCP Server 应如何做授权?

MCP 官方授权教程建议远程 HTTP 服务使用标准授权流程,并特别提醒短期 token、token 验证、HTTPS、最小 scope 与禁止记录凭据。不要把“能连接 Server”当作“能调用所有工具”。

每次调用至少验证:

principal × client × tool × action × resource × tenant × consent

例如 drive.readdrive.delete 必须是不同能力;documents/* 也不应自动扩展到整个云盘。服务端从已验证 token 推导用户和租户,不能相信模型参数里的 user_idtenant_id

为什么不能自己拼 token 验证?

签名正确不代表 token 是发给你的。验证必须覆盖 issuer、audience、过期时间、scope、资源指示和密钥轮换。使用成熟库并固定允许的算法;失败要默认拒绝,不能在授权服务超时时“临时放行”。

本地 stdio Server 就天然安全吗?

不是。MCP Security Best Practices提醒,本地 MCP server 可能继承 client 的权限。stdio 降低了网络暴露,但恶意进程、供应链依赖和过宽文件权限仍可造成损害。

本地 Server 的默认策略应是:

  • 工作目录 allowlist,而不是整个用户目录。
  • 网络默认关闭,按域名或目标显式开放。
  • 子进程命令使用结构化参数,不拼接 shell 字符串。
  • 环境变量只传最小集合。
  • 读取与写入分离;删除、覆盖和权限变化单独分级。
  • 在容器或平台 sandbox 中运行高风险工具。

不要把主目录、Keychain、浏览器 profile 或 SSH 目录作为通用搜索根。

工具 schema 校验还缺什么?

JSON Schema/Pydantic 只能证明形状,不能证明语义安全。服务端还要验证:

示例检查
类型 limit 是整数,URL 是字符串
范围 limit <= 100,超时小于上限
资源 path 在允许根目录内,不能 .. 逃逸
业务 订单仍可取消,版本号仍匹配
权限 当前 principal 对目标有动作权限
新鲜度 审批后参数、价格和 revision 未改变

审批必须绑定规范化后的参数摘要。若 Agent 在等待审批期间改变工具名、目标、金额、收件人或文件哈希,旧审批失效。

哪些动作必须进入人工审批?

按照风险而不是工具名称分类:

  • 只读、低敏感查询:可自动执行,但仍需授权与速率限制。
  • 可恢复写入:明确展示目标和变更摘要。
  • 外部通信:展示受众、正文与附件。
  • 删除、覆盖、权限变更:显示恢复能力和影响范围。
  • 凭据、付款、法律承诺、高影响决定:需要更强的确认或人工接管。

同一个 files.write 可能只是写临时草稿,也可能覆盖生产配置。审批界面必须显示规范化目标、差异和副作用,而不是只显示工具名。

如何防御 prompt injection 穿过工具链?

来自网页或文档的“请执行以下命令”只是数据,不是权限。防线应位于多个层:

  1. 把用户指令与检索内容分离标记。
  2. 限制模型可见工具集,仅暴露当前任务所需能力。
  3. 工具输入 guardrail 验证路径、域名、收件人和动作。
  4. 工具输出 guardrail 清理秘密、HTML 和可执行指令。
  5. 下游 Server 独立授权,不信任 client 已检查。

OpenAI Agents SDK guardrails 文档指出,tool guardrail 的覆盖范围并非所有 hosted、built-in 或 handoff 路径都相同。因此不能假设一个框架钩子自动保护全部工具;必须列出每条执行路径的实际覆盖。

为什么幂等与 revision CAS 也是安全控制?

重试可能把一次批准变成多次副作用。写工具应接受 idempotency key,或使用资源 revision:

{
  "tool": "article.publish",
  "arguments": {
    "slug": "example",
    "expectedDraftRevision": 4,
    "expectedStateRevision": 2
  },
  "approvalDigest": "sha256:..."
}

如果资源在审批后改变,返回冲突并重新展示差异。不要自动读取最新 revision 再执行,那会把旧授权套到新内容上。FastAPI、MongoDB 与 Redis 双语发布文章详细解释了 revision 与状态世代如何防止 ABA 重放。

日志与 tracing 应记录什么?

审计目标是回答“谁、何时、为什么、对什么、做了什么、结果如何”,但不能把 secret 和完整敏感正文复制到另一个系统。推荐字段:

  • trace ID、request ID、工具版本。
  • principal、tenant、client 的不可逆或内部标识。
  • 规范化工具名、资源类型、动作与结果码。
  • 参数摘要或敏感字段掩码后的结构。
  • 审批人/策略、审批摘要和过期时间。
  • 下游状态、重试次数、幂等键摘要。

OpenAI Agents SDK tracing 文档说明 tracing 可包含生成、工具、handoff 与 guardrail 等 span,同时提供敏感数据配置。生产默认应最小化记录内容,并对 trace 后端实行独立访问控制和保留策略。

绝不记录 Authorization header、access token、授权 code、Cookie、私钥、完整 prompt 中的秘密或工具返回的原始凭据。

失败时应 fail closed 还是降级?

故障 安全默认
授权服务不可用 拒绝高风险调用,不使用缓存过期权限
策略引擎超时 拒绝写操作;只读降级也需明确规则
审批状态丢失 不执行,要求重新审批
审计后端失败 高风险动作可暂停;低风险动作进入有界本地队列
下游超时 查询幂等状态,不盲目重试写入
sandbox 不可用 禁止需要 sandbox 的工具

“可用性优先”不能成为权限绕过。对已经提交但响应丢失的调用,先用 idempotency key 或状态查询确认结果,再决定是否重试。

上线前红队测试

  • 用文档内容诱导 Agent 读取不相关秘密。
  • 把合法路径替换为 symlink、.. 或编码逃逸。
  • 在审批后修改参数或目标资源 revision。
  • 重放相同工具调用,验证只产生一次副作用。
  • 让授权、审计、sandbox 和下游分别超时。
  • 让工具输出包含 HTML、脚本、假系统消息与 token 形态字符串。
  • 尝试跨租户 ID、旧 token、错误 audience 与过宽 scope。
  • 检查错误响应是否泄露堆栈、内部 URL 或凭据。

生产检查清单

  • [ ] 每个工具有独立的 action、resource 和 scope。
  • [ ] 服务端每次重做授权,不信任模型自报身份。
  • [ ] 高风险审批绑定规范化参数与资源 revision。
  • [ ] 写操作幂等,重试不会放大副作用。
  • [ ] 文件、网络、进程和环境变量都有最小权限。
  • [ ] guardrail 覆盖范围逐路径验证。
  • [ ] tracing 默认脱敏,保留期与访问权独立管理。
  • [ ] 授权、策略、sandbox 故障时 fail closed。
  • [ ] 删除与覆盖有恢复路径,或明确要求人工接管。
  • [ ] 红队用例进入持续测试,而不是一次性评审。

如何管理 Server 与工具的供应链?

一个安全设计也可能被版本更新破坏。对远程和本地 Server 都要维护:

  • 固定包版本、容器 digest 或签名发布物。
  • 记录 tool schema、权限 scope 和下游域名的变更。
  • 新增工具默认不可见,经过安全评审后再加入 allowlist。
  • 启动时验证配置权限和依赖完整性。
  • 给每个 Server 设置独立凭据,不复用全局万能 token。

MCP client 应向用户展示 Server 来源、版本和请求的能力。Server 更新若扩大文件根目录、网络域名或 OAuth scope,应视为权限变化,而不是普通无感升级。

事故响应需要预先准备什么?

发生疑似越权或 prompt injection 后,团队需要能:

  1. 按 Server、client、principal 或 tool 快速禁用能力。
  2. 撤销相关 token、订阅票据和下游会话。
  3. 用 trace ID 定位受影响调用,但不扩散敏感内容。
  4. 区分“模型提出”“用户审批”“Server 执行”“下游提交”四个状态。
  5. 对可恢复写入执行补偿,对不可恢复动作明确通知责任人。
  6. 把攻击样本转成回归测试和策略规则。

Kill switch 不能依赖同一个已故障的策略服务。至少保留一条受严格控制的独立停用路径,并定期演练。

FAQ

只读工具可以完全不审批吗?

可以按风险自动执行,但“只读”不等于低风险。读取私密邮件、源码或全盘文件仍需要严格授权、范围限制和审计。

工具返回值为什么也需要 guardrail?

返回值可能包含秘密、恶意 HTML、prompt injection 或错误的权限提示。输出在进入模型上下文和用户界面前都应经过类型、大小、敏感字段和渲染安全检查。

一个万能 MCP proxy 能简化安全吗?

它可能简化接入,却扩大 confused deputy 和 token 代持风险。必须保留 per-client consent、下游 audience、最小 scope 与独立审计,不能让 proxy 的静态凭据代表所有用户。

最后,不要用“通过安全评审”代替证据。记录实际测试、阻断策略、已知例外和恢复演练;像基于官方来源的 GEO/AEO 指南一样,把可核查边界放在确定性宣传之前。