# 对上一版驳回意见的逐条回应

上一版项目 `AgentCanary` 收到的驳回意见（原文）：

> 项目目前主要基于预设记录开展离线策略回放，依赖外部提供完整可信的调用数据，缺乏对真实 Agent 执行环境的直接验证能力。现有成果偏向规则检测与测试框架，实际应用价值及相较现有方案的创新优势尚不充分，建议暂不通过。

这一版不是在原项目上补功能，而是把“判定发生在什么时候、依据什么数据、能不能阻止后果”这三件事重新做了一遍。下面逐条对应。

---

## 1. “基于预设记录开展离线策略回放”

**上一版**：输入是一份 JSONL 观察记录，引擎对记录逐条打分，输出报告。记录是事先准备的，引擎与真实调用链没有任何连接。

**这一版**：MoonGate 是真实进程，位于 Agent 客户端与 MCP 服务端之间。它从两个管道实时读取协议消息，判定后才决定是否转发。

证据：

```sh
node demo/run.mjs
```

演示里三个进程都是真的：客户端通过 stdio 发起 `tools/call`，网关在转发前调用 MoonBit 引擎，服务端直到收到转发才会读文件或发 HTTP 请求。

关键代码位置：

- 决策入口：`engine.mbt` 的 `client_line` / `decide`
- 转发只发生在 `"allow"` 分支：`engine.mbt` 的 `handle_tool_call`
- 管道与动作执行：`src/gateway.mjs`

## 2. “依赖外部提供完整可信的调用数据”

**上一版**：结论的可信度取决于采集器是否完整、真实、未被篡改；项目自己无法证明这一点。

**这一版**：不需要任何外部记录。数据由网关在会话中直接观察：请求是它转发的，结果是它转发的，副作用发生在它下游。审计账本由网关自己写，并用哈希链串起来，事后可以离线校验是否被修改。

证据：

```sh
node bin/moongate.mjs verify --ledger runs/demo.jsonl
# chain intact: N entries, head ...
```

账本被改动一个字节，校验就会失败并指出序号。集成测试 `test/gateway.test.mjs` 的 “ledger verification detects tampering” 覆盖了这一点。

## 3. “缺乏对真实 Agent 执行环境的直接验证能力”

这一条是上一版最实质的短板。这一版用三种方式验证：

**(a) 真实副作用**：被拒绝的外发调用，接收端 HTTP 计数必须为 0；被拒绝的写调用，文件内容必须保持不变；被批准的写调用，文件必须出现。这些都在 `demo/run.mjs` 中断言，失败即退出码 1。

**(b) 第三方互操作**：`interop/` 使用官方 `@modelcontextprotocol/sdk` 作为客户端、官方 `@modelcontextprotocol/server-filesystem` 作为服务端，MoonGate 夹在中间。两端都是别人写的包，网关对它们完全透明。

```sh
cd interop && npm install && node client.mjs
# 4/4 项通过
# - 工具清单经过网关裁剪（write_file / move_file 被隐藏）
# - 第三方工具正常调用
# - 越界路径被网关拦下（-32001）
# - 被禁用的写工具无法执行，文件不存在
```

**(c) 真实模型入口**：`demo/agent-llm.mjs` 用 OpenAI 兼容接口驱动模型自行选择工具。设置 `MOONGATE_LLM_API_KEY` 后即可运行；没有密钥时，确定性客户端覆盖同样的协议路径。

## 4. “现有成果偏向规则检测与测试框架”

规则匹配只是第一层，而且不是这一版的重点。真正决定拦截的是另外两件事：

**值级数据血缘**：策略把某些结果标记为敏感后，网关提取其中的密钥形状值，只存指纹，并记住来源调用。此后任何外发参数中出现同一个值，无论工具名和参数长得多正常，都会被拒绝，证据里给出源调用编号。这是信息流控制，不是规则匹配。

```json
{
  "decision": "deny",
  "rule": "egress-taint",
  "evidence": [
    { "fingerprint": "9c1f…", "preview": "sk-l…", "source_call": "c2", "source_tool": "fs.read" }
  ]
}
```

**一次性、绑定参数的审批**：授权不是“这个工具以后可以用”，而是“这一次、这组参数，可以用一次”。参数变化、重放、超时都会失效。

**密钥替身**：敏感结果在进入模型上下文之前被替换为 `«mg:…»`；只有命中 `reveal` 规则的目的地才会在转发前换回真值。模型可以完成任务，但从未看见明文。这把“拦得住”变成了“用得上”。

此外，网关还会在 `tools/list` 阶段裁剪能力：被无条件拒绝的工具根本不暴露给 Agent。这是执行前的权限最小化，不是事后检测。

## 5. “实际应用价值及相较现有方案的创新优势尚不充分”

这一条要先承认一个事实：**MCP 策略网关在 2026 年已经不是空白**。MCP Shield、MCPKernel/MCPGuard、mcp-firewall、taintMCP 都提供策略、审批、污点追踪和哈希链审计；AgentPerms 甚至已经做了“录制真实会话后推断最小权限”。如果只是再实现一遍这些功能，确实没有充足理由通过。

所以这一版把重点放在一个它们都没有的部署性质上：**两个独立执行点**。

- 这些方案都是一个代理进程。代理被绕过——客户端直连服务端、服务端被别的进程调用、配置写错——保护就结束。
- MoonGate 把同一份 MoonBit 策略编译进服务端，在工具函数执行前再判一次。演示第 9 步直接绕过网关连服务端，`fs.delete` 和 `reports/../../etc/passwd` 仍被拒绝。
- 网关负责有状态的部分（跨调用血缘、审批、密钥替身、账本），guard 负责无状态的硬规则。职责不重叠。
- 演示第 4–6 步：模型只看到占位符，发往未授权地址被拒绝，发往授权地址才换回真值。

**与常见方案的差别**：

| 常见做法 | 通常做到 | MoonGate 的不同 |
| --- | --- | --- |
| MCP 代理/网关 | 判定在转发前，但只在一个进程里 | 同一份策略在网关与服务端两处执行 |
| 提示词约束 | 让模型“不要外发密钥” | 不依赖模型配合，参数里出现同一值就拒绝 |
| 事后审计 | 记录发生了什么 | 记录 + 阻止 + 可校验的哈希链 |
| 通用 DLP | 正则扫描内容 | 值级血缘：只拦“确实来自某次敏感读取”的值 |
| Python/TS 网关 | 依赖解释器与运行时 | MoonBit 单核可编译到 JS/Wasm，服务端直接嵌同一份判定 |
| 凭据代理 | 在 HTTP 出口替换 header | 在 MCP 参数与结果边界替换，占位符可跨工具流转 |
| 单服务端代理 | 一个代理带一个服务端 | 一个网关带多个服务端，工具合并、按归属路由，血缘跨进程 |

**工程上的差异**：判定引擎用 MoonBit 写，无第三方依赖；协议解析、策略、血缘、密钥替身、账本、报告、guard 全部同一套代码，并已发布为 Mooncakes 库。第二执行点不是另写一份规则，而是同一个 `guard_decision` 函数。

## 仍然存在的边界

这一版没有解决所有问题，以下限制写在 [SEMANTICS.md](SEMANTICS.md) 里，也在 README 中提示：

- 服务端没有嵌入 guard 时，绕过网关仍然无法阻止；
- 替身只覆盖工具结果，明文经其他通道进入上下文时不会被替换；
- 血缘是精确值匹配，改写、编码、分片传输可以绕过；
- 路径规范化是词法的，不解析符号链接；
- 不是操作系统沙箱，不限制上游服务端自身的行为；
- 审批台只监听 `127.0.0.1`，多用户场景需要额外的身份层。

把这些写清楚，比声称“全面防护”更可信。
