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 tools/list 的五个只读工具与双轮验证摘要

一、最终效果:不是“能启动”,而是五层门禁同时过

本次验收的通过条件包括:

  1. 真实 MCP stdio 初始化成功,并实际回读 tools/list
  2. 工具集合必须精确等于五个只读工具;
  3. 根内相对路径允许,越界路径拒绝;
  4. 工具响应、落盘 JSON 都不得出现主机 D 盘绝对路径、用户名、outside 诱饵完整值或合成凭据完整值;
  5. 两轮去掉耗时与 RSS 等波动字段后,规范化 JSON 的 SHA-256 必须一致。

冻结实测中,安装退出码为 0,两轮 pnpm verify 退出码为 0。每轮定义 15 项,执行 14 项;普通 Windows 目录 symlink 因 EPERM 无法创建,被明确标记为跳过,而不是算作通过。Windows Junction 创建成功,越界读取被拒。

二、环境与项目结构

实测环境如下:

项目冻结值
操作系统Windows,win32/x64
Node.jsv24.14.0
pnpm11.16.0
Repomix1.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_directory
  • file_system_read_file
  • grep_repomix_output
  • pack_codebase
  • read_repomix_output

工具集合采用精确匹配,少一个或多一个都会让门禁失败。这样能避免“文档说是只读”但运行时注册面发生漂移。

五、15 项路径矩阵怎么设计

矩阵既要有正向样本,也要有负向样本。只有全部试拒绝,无法证明根内正常功能没有被一并破坏。

Repomix MCP sandbox 允许根内相对路径并拒绝盘符、UNC、目录穿越和 Junction 越界的矩阵

正向用例包括根文件、嵌套文件、./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 / 1415 / 14
Junction 越界拒绝拒绝
symlinkEPERM 跳过EPERM 跳过
结构一致通过通过

独立复检这轮运行耗时为 5856.175 ms5643.491 ms,采样到的 Repomix 峰值 RSS 为 90,464,256 B85,553,152 B。这些只是两次本机夹具观测,不是性能基准,也不应外推到其他仓库或机器。

去掉耗时、RSS 等波动字段后,两轮规范化 JSON 的 SHA-256 都是:

DAF3ECFDCCD24EF5E451B774C5DF81BE2D1E589B34ECE4BCB5451694F56037CF

本轮发布冻结的原始 run-1.jsonrun-2.json SHA-256 分别为 89F62142A77C07B1D3FF9838D5BF0DC51A481E2095D0B725146C579774B891812EB2E7B158CDF802746A5EB79401D2A9E08C4196C90D9988419E5890877DBCEDsummary.json SHA-256 为 3EA66AECA81B81C19EF618C42A08859DB2AFD9A681A8B4E88FE20CB0B9CD77CE。原始 run 包含耗时和 RSS,所以它们的哈希不要求彼此相同;复现性门禁比较的是规范化结果。

两轮规范化 SHA-256 一致以及四类持久证据敏感值零命中的断言

持久证据目录进一步扫描了四类敏感内容: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
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐