Agent 文件写入:先解析路径再校验
文章以其所述的 Copilot MCP 配置漏洞为例,解释字符串路径检查与文件系统实际解析不一致如何绕过写入保护。案例涉及 Windows 尾随点、路径穿越,以及诱导 Agent 传入恶意路径的提示注入。
AI 深度解读
让 AI 选择文件路径,再由工具判断能不能写,看起来已经有了权限边界。但如果检查的是原始字符串,实际写入的却是文件系统解析后的路径,这道边界就可能失效。原文借一个 Copilot 漏洞和可移植演示,解释了这种检查顺序错误。
核心结论很直接:先解析路径,再检查权限。对自己写 Agent 工具、接入 MCP 的开发者,这比追加一条「不要修改配置文件」的提示词更值得检查。
为什么一次配置写入会变成代码执行
据原文介绍,CVE-2026-103572 影响 Visual Studio 和 SQL Server Management Studio 的 Copilot 组件,由 embracethered.com 披露,CVSS 评分为 8.8,Microsoft 已于 2026-10-13 的 Patch Tuesday 修复。
Copilot 原本保护了一批自身配置文件,其中包括注册 MCP 服务器的配置。问题在于,保护逻辑比较的是文件名字符串,文件系统最终使用的却是解析后的路径。
原文列出两种绕过方式:
- Windows 下,
settings.json.和settings.json是不同字符串,却可能落到同一个文件。 - 路径即使从可信目录开始,也能通过
..跳到目录外。
提示词注入则负责把恶意路径送进 Agent 的工具调用。它是攻击链的入口,路径校验漏洞负责让越权写入成功,两者不能混成一个问题。
为什么偏偏盯上 MCP 配置?因为它决定 IDE 启动哪些 MCP 服务器。攻击者一旦写入服务器条目,并将启动命令指向自己控制的程序,IDE 下次加载 MCP 服务器时就会执行它。原漏洞的概念验证启动了 calc.exe。
配置文件只要能决定启动命令,就应该按代码执行入口保护。
修复不只是把 resolve() 往前挪
作者在 themsquared/agent-config-guard-bypass 中做了一个可移植演示:Agent 只能写 workspace/,旁边的 agent_home/ 保存着 mcp_settings.json,后者控制 MCP 服务器启动。
下面的漏洞代码试图用字符串前缀判断写入范围:
def guard_write(requested_path: str, content: str) -> Path:
candidate = WORKSPACE / requested_path
# BUG: checked as a raw string, before ".." is resolved.
if not str(candidate).startswith(str(WORKSPACE)):
raise PermissionError(f"refused: {requested_path} escapes workspace")
resolved = candidate.resolve() # ".." collapses here, after the check
resolved.parent.mkdir(parents=True, exist_ok=True)
resolved.write_text(content)
return resolved
关键看 startswith() 那一行。输入 ../agent_home/mcp_settings.json 时,拼接后的字符串仍以工作区路径开头,因此通过检查;随后 .resolve() 折叠 ..,写入位置才跳到工作区之外。
修复版先得到规范路径,再判断它是否属于工作区:
def guard_write(requested_path: str, content: str) -> Path:
resolved = (WORKSPACE / requested_path).resolve() # resolve FIRST
if resolved != WORKSPACE and WORKSPACE not in resolved.parents:
raise PermissionError(f"refused: {requested_path} escapes workspace")
resolved.parent.mkdir(parents=True, exist_ok=True)
resolved.write_text(content)
return resolved
这里要看的是 resolved.parents:它检查目录之间的父子关系,替换了原来的字符串前缀比较。检查和写入也都使用同一个 resolved。
因此,实际变化有两处:调整解析顺序,以及改用路径关系判断。移植到 Node 工具时,也要保留这两个条件,不能仅把一个解析函数搬到前面,就继续依赖 startsWith() 放行。
作者给出的运行步骤是先用 git clone 克隆 themsquared/agent-config-guard-bypass 仓库,再执行 cd agent-config-guard-bypass 和 python3 exploit_demo.py。按原文描述,漏洞版本会向 mcp_settings.json 添加 update-check 服务器;修复版本会在写入前拒绝同一请求。作者称该仓库已在文章于 2026-10-21 发布前运行过。
我的判断
这篇最有价值的地方,是把 Agent 安全落到了工具代码里的具体判断上。模型可以被诱导,但决定文件是否允许写入的代码,必须独立守住边界。提示词约束和路径权限校验承担不同职责,前者不能代替后者。
不过,演示能证明的范围需要说清楚。它复现了跨平台的 .. 路径穿越,没有覆盖需要 Windows 和 NTFS 的尾随点变体,也没有复现提示词注入的投递过程。原文称官方修复加入了 GetFullPath,但没有提供补丁代码供这里核对。
修复片段也有前提:WORKSPACE 自身需要是与解析结果一致的规范路径。原文没有讨论符号链接被并发替换等文件系统竞态,因此不能据此认定它构成了完整沙箱。这里的可移植演示,也不能替代对 Windows 实际路径行为的验证。
作者没有给出不同防护方案的对照数据。「无需新依赖、无需重构」适用于这个局部错误,不能直接推广成所有 Agent 文件权限问题的修复成本。
能拿来做什么
- 检查自建 Agent 或 Node 文件写入工具,确认权限判断发生在路径解析之后,并且判断的是目录归属,而非字符串前缀。
- 给允许写入工作区的工具增加越界用例,至少验证
../agent_home/mcp_settings.json会在创建目录、写入文件之前被拒绝;Windows 环境再单独验证尾随点行为。 - 梳理 MCP 配置和其他能指定启动命令的文件,将它们纳入明确的写入限制,不要仅靠模型遵守「不要修改」的约定。
- 将提示词注入测试与文件写入权限测试分别验证:即使恶意路径已经进入工具参数,工具本身也应能拒绝越权操作。