mcpField
M
mcp-confirm

mcp-confirm

本地服务
les-k.github.io安全04 周前更新

关于 mcp-confirm

mcp-confirm 是一个面向需要用户确认的 MCP 服务的安全示例,演示如何防止确认被重复使用、提示内容被伪装,以及确认后文件被替换。它通过限定文件根目录和执行前复查资源来实现这些检查。

主要功能

  1. 01确认记录仅可消费一次
  2. 02清理并截断用户可见提示
  3. 03执行前复查文件状态
  4. 04拒绝根目录外路径
  5. 05阻止符号链接替换

适用场景

  1. 01防止一次性操作的确认被重复使用
  2. 02降低确认对话中的提示注入风险
  3. 03确认后检查待删除文件是否被替换

MCP 相关

该服务在 MCP 的请求状态边界之外补充单次确认记录、提示清理和资源复查。示例工具仅删除启动时指定根目录内的文件。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"les-k-mcp-confirm": {
"command": "uvx",
"args": [
"mcp-confirm"
]
}
}
}
启动命令uvx mcp-confirm

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypimcp-confirm0.2.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

mcp-confirm

CI Python License: MIT

通俗地说: 当 AI 问“你确定吗?”时,MCP SDK 已经会阻止这项批准被用于不同的操作,但不会阻止同一项批准被使用两次。本项目弥补了这一缺口,以及 SDK 留下的另外两个缺口。


请先阅读:这个问题的大部分已经解决

2026-07-28 的 MCP 规范新增了多轮往返请求,因此工具可以在调用过程中暂停并请求用户确认。批准会通过客户端以不透明的 requestState 传回;规范要求服务端将其视为必须防范攻击者控制的数据。

Python SDK 默认会为你处理这件事,适用于每个 MCPServer。 RequestStateBoundary 会无条件追加到中间件链中——如果你没有提供密钥,它会使用临时密钥。它使用 AES-256-GCM 对状态进行封装,并将其绑定到方法、目标、调用参数的摘要、受众和经过身份验证的主体,同时设置 TTL。

因此,大家最先想到的攻击——批准删除 cache.txt,然后将该批准重放到 thesis.txt 上——SDK 已经会拒绝。 你不需要为此使用库,也不应该自己编写一个。

尽管如此,本仓库最初还是写了一个:250 行的 HMAC、TTL 和参数绑定代码,作为主打功能发布,而它在发布当天就已多余。相关记录放在文末,并未悄悄删除。

SDK 不会做什么

有三个缺口,每个都有对应测试。

1. 它会绑定状态并使其过期,但从不消耗状态。

在 TTL 有效期内,同一项批准无论提交多少次都会验证通过。边界每次进行的检查都会通过。规范明确指出这是有意设计的,并说明其余工作由你负责:

请注意,这些措施会限制重放窗口,并防止跨用户和跨请求重用,但本身并不能保证只能使用一次。对于必须确保给定 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 表示 此软件包拒绝了请求。

攻击拒绝方测试
将审批重放到另一个文件上SDKtest_the_sdk_refuses_a_confirmation_replayed_onto_another_file
伪造状态,或使用另一把密钥对状态进行封装SDKtest_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 上运行,这是有意为之:审批后替换文件的测试需要 符号链接,而且如果这些测试在 Ubuntu 上被报告为跳过,构建就会失败。

安装

pip install mcp-confirm

运行

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

如果未指定 --root,服务会拒绝每个请求,而不是默认使用任何路径。根目录在启动时固定, 且不会由 Agent 选择。无需签名密钥——MCPServer 会自带密钥。

已知限制

  • 账本存储在内存中,因此单进程下运行正确,但置于负载均衡器之后就会出错。SDK 支持在副本之间共享密钥 (RequestStateSecurity(keys=[...]));在这种配置下,副本 B 会拒绝由副本 A 签发的审批。它会以故障关闭的方式处理——这是更安全的方向—— 但用户看到的只是审批莫名其妙地失效了。多进程部署需要 Redis 或带有 原子比较并删除操作的数据库行。此行为有对应的测试。
  • **演示工具刻意保持精简。**它只删除一个文件。有意思的代码在 singleuse.py 和 prompt.py 中。
  • **没有 CVE 支持这一做法。**MRTR 发布仅数周,因此这里的实现依据是规范自身的 MUST/SHOULD 列表以及对 SDK 的阅读,而非已公开的安全事件。

0.1.0 版本的问题

保留这段记录,因为只记录成功的仓库并不能证明任何事情。

0.1.0 重新实现了 SDK 已经完成的功能。 state.py 包含 250 行 HMAC 签名、TTL,以及对主体、方法和参数的绑定逻辑——这些都重复实现了 RequestStateBoundary,而且做得更差:SDK 使用经过身份验证的加密,这里却使用 HMAC;没有受众绑定,也不支持密钥轮换。

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

CI 还单独发现了 0.1.0 中的一个真实错误:符号链接检查在 Path.resolve() 之后运行,因此检查的是链接的目标,而不是链接本身。若有人将其替换为允许的根目录内的另一个文件,程序就会删除该文件。问题已修复,并为原实现从未测试过的变体添加了测试。

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

许可证

MIT。

作者

由 Leslie Kadenge 开发。我独立审查 MCP 服务的安全性;我对 13 个生产环境服务的公开调查见 les-k.github.io/field-notes.html。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。