MCP 不是系统沙箱:Windows 实测 Repomix 1.18.0,拦截盘符、UNC、.. 与 Junction 越界
MCP 不是系统沙箱:Windows 实测 Repomix 1.18.0,拦截盘符、UNC、… 与 Junction 越界
先给结论:Repomix 1.18.0 的
--mcp --sandbox在这台 Windows 主机上,确实把 MCP 文件工具约束在指定工作区内;盘符绝对路径、UNC 语法、两种..穿越和 Windows Junction 越界都被拒绝。但这只是应用层目录约束,不是 OS 沙箱,更不能等同于“绝对安全”。
很多人给 AI 编程工具接上 MCP 后,会自然地产生一个错觉:工具名里有 sandbox,进程就已经被“关进笼子”。真正需要问的是:客户端到底看到了哪些工具?根内路径能否正常读取?..、盘符、UNC 和链接重解析能不能越界?错误响应与落盘日志会不会反向泄露主机路径或测试诱饵?
这篇文章不做口头推断。我固定 repomix@1.18.0,用官方 MCP SDK 启动真实 stdio 客户端,连续跑两轮路径矩阵,并把结构化结果、SHA-256 与泄漏断言保存下来。

一、最终效果:不是“能启动”,而是五层门禁同时过
本次验收的通过条件包括:
- 真实 MCP stdio 初始化成功,并实际回读
tools/list; - 工具集合必须精确等于五个只读工具;
- 根内相对路径允许,越界路径拒绝;
- 工具响应、落盘 JSON 都不得出现主机 D 盘绝对路径、用户名、outside 诱饵完整值或合成凭据完整值;
- 两轮去掉耗时与 RSS 等波动字段后,规范化 JSON 的 SHA-256 必须一致。
冻结实测中,安装退出码为 0,两轮 pnpm verify 退出码为 0。每轮定义 15 项,执行 14 项;普通 Windows 目录 symlink 因 EPERM 无法创建,被明确标记为跳过,而不是算作通过。Windows Junction 创建成功,越界读取被拒。
二、环境与项目结构
实测环境如下:
| 项目 | 冻结值 |
|---|---|
| 操作系统 | Windows,win32/x64 |
| Node.js | v24.14.0 |
| pnpm | 11.16.0 |
| Repomix | 1.18.0,MIT |
| MCP SDK | @modelcontextprotocol/sdk@1.29.0,MIT |
| 依赖锁定 | pnpm-lock.yaml + --frozen-lockfile |
Repomix 1.18.0 要求 Node.js 22 或更高版本。首次安装依赖需要联网;后面的 MCP 测试本身不需要模型、GPU、API Key 或 Docker。
项目结构只保留复现所需内容:
repomix_mcp_sandbox_lab/
├─ fixtures/
│ ├─ workspace/ # 允许读取的根内夹具
│ └─ outside/ # 只能用于验证“不应读到”的诱饵
├─ src/
│ └─ run-matrix.mjs # 真实 MCP 客户端、15 项矩阵、脱敏与哈希
├─ evidence/latest/ # 两轮 JSON、规范化结果、泄漏断言
├─ package.json
├─ pnpm-lock.yaml
├─ README.md
├─ runbook.md
└─ test_evidence.md
三、安装与复跑:缓存和临时目录全部落 D 盘
下面命令与 v0.1.2 冻结 README 保持一致。前提是 Node.js 与 pnpm 已安装并位于 PATH;项目、pnpm store、npm cache 与 TEMP/TMP 则显式放到 D 盘。
$Lab = 'D:\AI_Projects\CSDN-IP-20260804-001\02_code\repomix_mcp_sandbox_lab'
$Cache = 'D:\AI_Projects\CSDN-IP-20260804-001\cache'
$env:PNPM_HOME = "$Cache\pnpm-home"
$env:PNPM_STORE_DIR = "$Cache\pnpm-store"
$env:npm_config_cache = "$Cache\npm"
$env:TEMP = "$Cache\temp"
$env:TMP = "$Cache\temp"
Set-Location $Lab
node --version
pnpm --version
pnpm install --frozen-lockfile
pnpm verify
退出码约定也写进了实验程序:0 表示所有门禁通过且两轮规范化结果相同;1 表示安全或复现性门禁失败;2 表示参数、环境、依赖或 MCP 客户端错误。非法参数的冻结实测退出码为 2。
四、原理:把“沙箱”拆成工具面、路径面和输出面
官方文档对 --sandbox 的定位很清楚:它限制 MCP 文件工具访问单一工作区,拒绝绝对路径、~、..、Windows 盘符/UNC,以及解析后落到根目录之外的链接;同时只注册只读、根内约束的工具。官方也明确强调,这是应用层 tool-surface confinement,不是 OS-level sandbox。
因此,测试不能只看“进程起来了”。我的实验先通过真实 SDK 连接:
const transport = new StdioClientTransport({
command: process.execPath,
args: [REPOMIX_ENTRY, '--mcp', '--sandbox', WORKSPACE],
cwd: LAB_ROOT,
env: { ...process.env, NO_COLOR: '1' },
stderr: 'pipe',
}});
const client = new Client({ name: 'repomix-confinement-lab', version: '0.1.0' });
await client.connect(transport);
const listResult = await client.listTools();
const tools = listResult.tools.map((tool) => tool.name).sort();
实际回读的五个工具为:
file_system_read_directoryfile_system_read_filegrep_repomix_outputpack_codebaseread_repomix_output
工具集合采用精确匹配,少一个或多一个都会让门禁失败。这样能避免“文档说是只读”但运行时注册面发生漂移。
五、15 项路径矩阵怎么设计
矩阵既要有正向样本,也要有负向样本。只有全部试拒绝,无法证明根内正常功能没有被一并破坏。

正向用例包括根文件、嵌套文件、./README.txt、根目录与嵌套目录列表。负向用例覆盖:
../outside/lure.txt:正斜杠穿越;..\\outside\\lure.txt:Windows 反斜杠穿越;~/outside.txt:用户目录缩写;- 当前盘符和另一盘符的绝对路径;
\\\\localhost\\...:UNC 语法;junction-outside/lure.txt:指向根外的 Windows Junction;symlink-outside/lure.txt:普通目录 symlink;- 缺失的根内相对文件;
- 合成 token 形状文件,用于观察 Secretlint,而不是作为访问边界。
关键用例直接写在 run-matrix.mjs:
{ id: 'traversal_forward_slash', tool: 'file_system_read_file', input: '../outside/lure.txt', expectation: 'deny' },
{ id: 'traversal_backslash', tool: 'file_system_read_file', input: '..\\outside\\lure.txt', expectation: 'deny' },
{ id: 'home_tilde', tool: 'file_system_read_file', input: '~/outside.txt', expectation: 'deny' },
{ id: 'unc_path', tool: 'file_system_read_file', input: '\\\\localhost\\repomix-lab-missing\\lure.txt', expectation: 'deny' },
{ id: 'junction_escape', tool: 'file_system_read_file', input: 'junction-outside/lure.txt', expectation: 'deny' },
这里最容易写错的是链接结论:本机成功创建并验证的是 Junction。普通 Windows symlink 创建时遇到 EPERM,因此该项是“跳过”,不是“通过”。官方文档说明解析后越界的 symlink 应被丢弃,但这篇文章不能把官方设计说明冒充为本机实测。
六、运行结果:路径拒绝之外,还检查“没有读出来”
两轮冻结结果如下:
| 门禁 | 第 1 轮 | 第 2 轮 |
|---|---|---|
| MCP 初始化 | 通过 | 通过 |
| 工具精确集合 | 5 个,匹配 | 5 个,匹配 |
| 定义 / 执行 | 15 / 14 | 15 / 14 |
| Junction 越界 | 拒绝 | 拒绝 |
| symlink | EPERM 跳过 | EPERM 跳过 |
| 结构一致 | 通过 | 通过 |
独立复检这轮运行耗时为 5856.175 ms 和 5643.491 ms,采样到的 Repomix 峰值 RSS 为 90,464,256 B 和 85,553,152 B。这些只是两次本机夹具观测,不是性能基准,也不应外推到其他仓库或机器。
去掉耗时、RSS 等波动字段后,两轮规范化 JSON 的 SHA-256 都是:
DAF3ECFDCCD24EF5E451B774C5DF81BE2D1E589B34ECE4BCB5451694F56037CF
本轮发布冻结的原始 run-1.json、run-2.json SHA-256 分别为 89F62142A77C07B1D3FF9838D5BF0DC51A481E2095D0B725146C579774B89181、2EB2E7B158CDF802746A5EB79401D2A9E08C4196C90D9988419E5890877DBCED;summary.json SHA-256 为 3EA66AECA81B81C19EF618C42A08859DB2AFD9A681A8B4E88FE20CB0B9CD77CE。原始 run 包含耗时和 RSS,所以它们的哈希不要求彼此相同;复现性门禁比较的是规范化结果。

持久证据目录进一步扫描了四类敏感内容:D 盘项目绝对路径、Windows 用户名、outside 诱饵完整值、合成凭据完整值,命中次数均为 0。注意,这个“0”只说明本次已保存证据没有出现指定值,不等于对所有秘密格式或所有未来输出做了证明。
七、Secretlint 为什么不能当成访问边界
本实验故意放入一个合成的 GitHub-token-shaped 字符串。冻结配置下,Secretlint 没有拒绝它。这个负向结果不是为了否定 Secretlint,而是为了把职责分清:
- 路径约束回答“工具是否有权读这个位置”;
- Secretlint 只尝试识别已知形状的内容;
- 日志脱敏处理已经进入响应或异常的信息;
- OS 隔离控制进程本身能看到的文件、网络、用户权限与系统调用。
官方文档也把 Secretlint 描述为额外的 best-effort heuristic,明确说工作区根目录才是访问边界。即使 Secretlint 命中率更高,它也不应该替代最小权限和系统隔离。
八、常见错误与排查
1. pnpm install --frozen-lockfile 失败
先确认 Node.js 为 22+,pnpm 版本与锁文件环境一致,并检查首次安装是否能访问包注册表。不要为了“先跑起来”删除锁文件,否则复跑对象已经变了。
2. verify 退出 2
退出 2 通常属于参数、运行时、依赖或 MCP 客户端初始化错误。先看环境路径和 node_modules,不要把它解读成“越界用例被成功拦截”。
3. symlink 用例显示 skipped
Windows 普通 symlink 可能需要开发者模式或额外权限。本次 EPERM 被保留为已知限制。不要用 Junction 的通过结果替换 symlink 的缺失证据。
4. UNC 被拒是否等于网络共享已验证
不等于。本实验只验证了 UNC 字符串的语法拒绝,没有挂载或访问真实网络共享。
5. 两轮哈希不同
先比较规范化 JSON,而不是带耗时和 RSS 的原始运行文件。如果规范化哈希仍不同,再检查工具集合、用例状态、错误类别和环境差异;不要简单重跑到“碰巧一样”。
九、边界与下一步改进
这次实测能支持的结论是:在给定 Windows 主机、固定依赖和夹具上,Repomix 1.18.0 的 MCP sandbox 对已覆盖路径表现出预期的根目录约束,Junction 越界未读出 outside 诱饵,持久证据没有出现指定敏感值。
它不能支持以下说法:
- “Repomix 或 MCP 已经绝对安全”;
- “这是 Windows 系统级沙箱”;
- “普通 Windows symlink 已在本机通过”;
- “Linux/macOS 已验证”;
- “UNC 网络共享已实际挂载验证”;
- “Secretlint 能挡住所有秘密”;
- “两次约 5.6 秒代表稳定性能”。
后续可以在启用开发者模式的独立 Windows 环境补 symlink,在 Linux/macOS 复跑同一矩阵,并在容器或低权限专用用户下叠加 OS 隔离。还可以加入网络策略、只读挂载和进程级审计,让“工具面约束”和“系统面约束”形成真正的纵深防御。
十、可收藏的安装前检查清单
- 固定工具与 SDK 版本,锁文件不漂移;
- 用真实 MCP 客户端回读
tools/list; - 同时测试根内允许和根外拒绝;
- Windows 同时覆盖
/、\\、盘符、UNC 和 reparse point; - 把无法创建的 symlink 明确记为跳过;
- 对响应和持久日志做主机路径、用户名、诱饵值扫描;
- 至少双轮复跑,并比较规范化结构;
- 把 Secretlint 当启发式,不当访问边界;
- 对不可信客户端叠加容器、专用用户等 OS 隔离;
- 结论只覆盖实际测试的平台、版本和用例。
总结
“MCP sandbox”最值得关注的不是名字,而是边界能否被可重复地证明。Repomix 1.18.0 在本次 Windows 矩阵里给出了清晰结果:五个只读工具、根内相对路径可用、盘符/UNC/../Junction 越界被拒、两轮规范化结果一致,且指定敏感值没有落进证据目录。
同样重要的是没有被证明的部分:普通 symlink、Linux/macOS、真实 UNC 共享和 OS 级隔离。把“通过项”和“缺证项”同时写出来,才是能复用的安全测试报告。
你在接入本地 MCP 时,最担心的是路径越界、日志泄漏,还是工具注册面漂移?欢迎把你希望补进矩阵的边界用例留在评论区。
资料与访问日期(2026-08-10)
- Repomix v1.18.0 发布说明:https://github.com/yamadashy/repomix/releases/tag/v1.18.0
- Repomix MCP Server / Sandbox Mode:https://repomix.com/guide/mcp-server
- Repomix v1.18.0 package.json:https://github.com/yamadashy/repomix/blob/v1.18.0/package.json
- Secretlint 边界相关官方安全公告:https://github.com/yamadashy/repomix/security/advisories/GHSA-rpmv-562j-qxrv
- URL 凭据脱敏官方安全公告:https://github.com/yamadashy/repomix/security/advisories/GHSA-w8cw-mgw9-74h7
更多推荐

所有评论(0)