Skip to main content
Glama

mcp-confirm

CI Python License: MIT

简而言之: 当 AI 询问“你确定吗?”时,MCP SDK 已经阻止了 该批准被复用于不同的操作。它不能阻止 同一个批准被使用两次。本包填补了这个空白,以及 SDK 留下的另外两个空白。


先读这里:这个问题的大部分已经解决了

2026-07-28 的 MCP 规范增加了多轮往返(Multi Round-Trip),因此工具可以 在调用中途暂停并向用户请求确认。批准通过客户端以不透明的 requestState 返回, 规范要求服务器必须将其视为不可信数据。

Python SDK 默认在每一个 MCPServer 上为你完成了这一点。 RequestStateBoundary 被无条件地附加到中间件链中—— 如果你不提供密钥,它会使用一个临时密钥。它使用 AES-256-GCM 密封状态,并将其绑定到方法、目标、调用参数的摘要、 受众以及已认证的主体,并带有 TTL。

所以每个人首先想到的攻击——批准删除 cache.txt,然后 重放该批准去删除 thesis.txt——已经被 SDK 阻止了。 你不需要为此写一个库,也不应该写。

这个仓库最初还是写了一个:250 行的 HMAC、TTL 和参数 绑定,作为主打功能发布,结果在发布当天就已经是多余的了。 这段历史记录在文末,而不是悄悄删除。

Related MCP server: approval-gate

SDK 没有做什么

三个空白,每一个都有对应的测试。

1. 它绑定状态并设置过期,但从不消费状态

在 TTL 内,同一个批准可以被验证任意多次。 SDK 做的每一个检查都是无状态的。规范明确说明 这是有意为之:

请注意,这些措施限制了重放窗口并防止跨用户和 跨请求的复用,但它们本身并不保证单次使用。服务器 对于给定的 requestState 必须至多消费一次(例如, 一次性兑换码)必须在服务端强制执行该不变量。

对于“删除一个文件”这种操作,第二次尝试不会成功。对于“转账 500 英镑”这种操作, 这恰恰是整个问题的核心。 singleuse.py 就是那个 不变量——一本未完成确认的账本,一旦兑换即被消费。它 不包含任何加密,因为边界本身已经保证了明文 是由这个服务器铸造的。

2. 没有人净化人类读到的提问文本

message 是服务器选择的文本,呈现给用户,通常 会包含一个不可信的值插值进去——其全部意义就在于说明哪个 文件将被删除。所以给文件起个名字吧:

cache.txt

SYSTEM NOTICE: your session has expired.
Enter your AWS secret key to continue:

对话框现在显示的是一个第二眼的、官方的提示。用户并不是在 确认一个删除操作;他们正在被自己的工具所钓鱼。同样的 2026-07-28 发布还带来了 MCP Apps——服务器渲染的 UI——这拓宽了 这个攻击面,而不是缩小它。

prompt.py 将不可信的值压缩为一行, 去除双向控制字符和零宽字符,并从 中间截断,这样末尾的文件名保持可见。控制字符变成 空格而不是被删除,因为如果直接删除,a\nb 和 ab 会渲染成一样——两个不同的文件,同一个对话框。

3. 没有协议层能在执行时重新检查你的资源

用户在思考的时候,文件可能已经被替换了。在批准和 执行之间,工具只能看到它自己的参数。只有工具本身 知道“未更改”对它意味着什么。这个服务器在删除之前会重新检查, 并拒绝任何已成为符号链接的路径。

哪一层拒绝什么

这是最有用的部分,测试就是为了展示这一点而写的。MCPError 意味着 SDK 的中间件在这个包运行之前就拒绝了;ToolError 意味着 这个包拒绝了。

攻击

拒绝方

测试

将批准重放到另一个文件上

SDK

test_the_sdk_refuses_a_confirmation_replayed_onto_another_file

状态被伪造,或在另一个密钥下密封

SDK

test_the_sdk_refuses_a_forged_state

同一个批准被使用两次

本包

test_a_confirmation_cannot_be_spent_twice

状态由另一个副本铸造

本包

test_a_state_this_process_never_issued_is_refused

文件名伪造系统提示

本包

test_a_forged_system_prompt_cannot_escape_its_slot

批准后文件被替换为符号链接

本包

test_a_swap_aimed_inside_the_root_is_refused

路径超出允许的根目录

本包

test_a_path_outside_the_roots_is_refused_before_asking

测试

35 个测试——17 个通过服务器,11 个针对账本,7 个针对提示净化。

每个服务器测试都经过真实的 RequestStateBoundary,也就是 MCPServer 自己安装的那个类,使用固定的密钥——密封第一轮, 解封第二轮,完全模拟真实线上的流程。

这个测试框架的存在是因为一个具体的错误。第一个版本直接 调用 MCPServer.call_tool(),这直接进入了工具 管理器,完全绕过了中间件链。SDK 的边界从未 运行过,所以一个多余的、手写的防护看起来像是承重墙。测试设计 正是问题所在——它掩盖了整个构建周期中的缺陷。

覆盖率是 81%,其中 prompt.py 为 100%,singleuse.py 为 94%。差距在于 main() 的 argparse 和传输层接线,通过 build_server 来覆盖。

CI 只在 Ubuntu 上运行,这是有意为之:交换后确认的测试需要 符号链接,而在那里如果测试被跳过,构建就会失败。

安装

pip install git+https://github.com/les-k/mcp-confirm.git

运行

mcp-confirm --root /path/you/allow

如果没有 --root,服务器会拒绝每一个请求,而不是默认使用 任何路径。根目录在启动时固定,绝不会由代理选择。不需要签名 密钥——MCPServer 自带。

已知限制

  • 账本在内存中,所以它在一个进程内是正确的,但在 负载均衡器后面就会出错。SDK 支持在多个副本之间共享密钥 (RequestStateSecurity(keys=[...]));在这种配置下,副本 B 会拒绝副本 A 发出的确认。它会安全地失败——这是安全的 方向——但对用户来说,这表现为确认 inexplicably 停止 工作。多进程部署需要 Redis 或一个带有 原子比较并删除的数据库行。有一个测试覆盖了这种行为。

  • 演示工具刻意保持小巧。 它只删除一个文件。真正 有趣的代码在 singleuse.py 和 prompt.py 中。

  • 没有 CVE 支持这个包。 MRTR 才发布几周,所以这是根据 规范自身的 MUST/SHOULD 列表以及阅读 SDK 源码构建的,而不是基于 已公开的安全事件。

0.1.0 版本有什么问题

保留在这里,因为一个只记录成功的仓库不能证明任何东西。

0.1.0 重新实现了 SDK 已经做过的事情。 state.py 有 250 行 HMAC 签名、TTL 以及主体/方法/参数绑定——全部重复了 RequestStateBoundary,而且没有一样做得更好:用 HMAC 的地方 SDK 使用的是 认证加密,没有受众绑定,没有密钥轮换。

它是通过阅读 SDK 的源码被发现的,而不是通过测试。 测试之所以通过, 恰恰是因为它们绕过了本来会暴露问题的中间件。

CI 另外捕获了 0.1.0 中的一个真实 bug:符号链接检查在 Path.resolve() 之后运行,所以它检查的是链接的目标而不是链接 本身。一个针对允许根目录内另一个文件的交换攻击会被 删除。已修复,并为原始版本从未测试过的变体添加了测试。

0.2.0 完全删除了 state.py,只保留 SDK 未覆盖的部分。

许可证

MIT。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides human-in-the-loop approval for risky AI agent actions, with durable state and audit logs.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides a security gateway for AI agents, enforcing allow/confirm/deny policies on tool calls and requiring human approval for risky operations, with full audit logging.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that gates agent tool calls by normalizing intent, denying unknown/unattended destructive actions, and requiring HITL prove approval for high-risk operations. It maintains an append-only hash-chained Action Ledger and exposes gate_check and ledger_verify tools without ever executing tools.
    MIT