Emetgate:夾在 LLM 與原始碼之間的驗證閘門
Emetgate 是 GitHub 上的開源驗證核心,在 Windows 上位於語言模型與 TypeScript/JavaScript 原始碼樹之間。模型只能提議修改,不能寫檔案;核心檢查雜湊、解析結果和影響範圍,必要時在沙箱中跑測試,再原子提交或拒絕。核心在無法證明安全時一律拒絕。README 稱專案仍處早期階段。
Emetgate 是什么
Emetgate 是 GitHub 上的开源项目(emetgate/emetgate),README 把它定位为“位于语言模型与源码树之间的确定性验证内核”。项目口号是“Nothing passes but the truth.”(唯有真相可以通过)。README 明确写道,项目处于早期阶段,范围刻意收窄:目前只支持 Windows,用 Zig 0.16.0 编写,处理 TypeScript 和 JavaScript 代码,并通过模型上下文协议(MCP)对外提供工具,因此 Claude Code 之类的 MCP 客户端可以直接接入。
它的核心思路很好懂。语言模型可以读代码,也可以针对某个符号提出修改建议,但不能写文件,也不能宣布任务已经完成。每一份提案都由一个小型内核检查,要么原子提交,要么带着理由被拒绝。README 把这一原则概括为:“模型提议,内核验证,未经验证的内容不会落盘。”
作者为什么要做它
README 列出了认真使用 LLM 生成代码的人都熟悉的几类问题:代码能编译却依然出错;模型在任务没做完时汇报“已完成”;三轮之前定下的规则被悄悄遗忘;会话开头商定的计划到结尾已经消失。作者认为,这些问题看似各自独立,其实出自同一个原因:模型与磁盘之间,没有任何环节负责检查模型的产出。
据 README 所述,当前的工具在自主性和速度上竞争,结果是未经验证的输出越来越多。模型自己无法弥补这个缺口,因为它是“采样器,不是神谕”,也没有持久记忆。于是 Emetgate 不给模型任何权力,把全部权力交给内核。README 还把这种做法类比为 LCF 风格的定理证明器:策略可以随意提出建议,但只有一个小而可信的内核才能产出定理。项目名字取自布拉格魔像的传说:魔像额头上写着 emet(“真理”)一词而获得生命,擦掉第一个字母就变成 met(“死”)。
一次修改如何通过“门”
README 描述了六个步骤。
1. **寻址。** 每个符号用引用(如 `Class.method` 或 `add`)加上当前内容的 128 位哈希来标识。提案必须写明自己依据的哈希。如果文件在此期间变了,哈希不再匹配,提案就被拒绝,所以模型无法覆盖它没看过的代码。
2. **解析。** 新的函数体按字节范围拼接进源码,整个文件用 tree-sitter 重新解析。
3. **守卫。** 内核检查结果能否干净地解析、函数体是否越出了花括号、是否为空或占位内容,以及目标范围之外的每个字节是否都没有变化。
4. **界定。** 内核计算改动的影响半径。这个分析是正向的、封闭世界的计数:只有当所有可能“逃逸”的途径都被排除,改动才算 `BOUNDED`。凡是无法解释的,一律算 `UNBOUNDED`。
5. **测试。** `UNBOUNDED` 的改动会应用到一份影子副本上,项目的测试命令在沙箱里针对它运行。沙箱是带有 kill-on-close、墙钟时间与内存限制以及输出上限的 Windows Job Object,命令在低完整性受限令牌下运行。如果这个令牌无法构建并验证,命令会被拒绝,而不是不受限地运行。测试失败则改动被拒绝,输出返回给模型。
6. **提交。** 通过的改动先写入预写日志,再做原子的写入并重命名。崩溃之后留下的要么是旧文件,要么是新文件,绝不会出现写了一半的文件。`recover` 命令会重放日志,并拒绝任何它无法证明的条目。
README 称内核是“失败即关闭”(fail-closed)的:当它无法证明某项改动安全时,就拒绝该改动。
MCP 工具与 lockdown 模式
服务器提供读取和修改两类工具。读取类包括 `emetgate_symbols`、`emetgate_skeleton`、`emetgate_read_symbol`、`emetgate_read_file`、`emetgate_list` 和 `emetgate_search`,后三者被限制在仓库之内。`emetgate_mutate` 只做结构验证并返回结果,不写入。`emetgate_try` 完成验证、过闸和提交。`emetgate_try_batch` 把多份提案当作一个整体。`emetgate_scan` 针对仓库度量一个检查表达式,不写任何东西。
命令 `emetgate lockdown` 会启动 Claude Code,并且只开放这些工具,因此模型除了这道门,没有任何通往磁盘的路径。仓库还附带一个 Claude Code 技能 `md-audit`:它把 CLAUDE.md 或 AGENTS.md 里的每条指令归类为可强制执行、等待机制、无法验证或信念,再用 `emetgate_scan` 度量可强制执行的那部分。它不会修改任何文件。
内核自身如何被验证
README 说,一个自己没有被验证过的验证层,“只是更精致的一种祈祷”。它列出两项做法。第一是变异测试:对守卫做变异(去掉检查、削弱条件、翻转比较),每个变异体都必须让测试套件失败。针对 `cas`、`boundedness`、`symbol` 和 `functions` 这几个引擎模块,README 给出的数字是 44 个变异体:37 个被杀死,4 个被证明等价,1 个作为纵深防御保留的冗余守卫,2 个仍未解决。第二是红队测试:针对越出花括号的函数体、过期哈希、被撕裂的日志条目、被污染的仓库配置以及访问仓库之外文件的尝试。
README 还给出一项 token 基准测试。在六个场景(其中两个是真实文件)上,使用 `o200k_base` 分词器,符号级提案所用的 token 中位数比查找替换式编辑少 1.80 倍,范围是 1.15 到 3.77 倍;在真实文件上收益为 1.15 到 1.17 倍。README 称节省 token 只是“副作用,而非重点”。
我们的分析
值得注意的是信任放在哪里。许多编码工具问的是如何让模型更可靠,Emetgate 问的是如何让模型的不可靠变得无害。由于检查都是输入的确定性函数,评审者可以阅读、测试并攻击它们。这与模型自己声称“任务完成”是完全不同的保证。
有两个细节值得一提。内容哈希把“模型改了过期的代码”从一个悄无声息的错误变成了一次被拒绝的提案。封闭世界的界定规则在不确定时默认去跑测试,用速度换取安全。
局限与未解问题
README 对局限相当坦率。能解析、不越界、测试也通过的代码,仍可能实现了错误的行为。测试闸的强度取决于测试本身。保证只对经过这道门的改动成立,其他工具做的编辑会绕过它,这正是 lockdown 存在的原因。低完整性令牌能阻止写到影子副本之外,但不限制读取和网络访问,所以恶意的测试命令仍可读取有权限的文件并联网;README 说计划用 AppContainer 来限制。架构、API 设计和用户体验则不是内核能检查的东西。
从状态表看,决策账本尚未以 MCP 工具的形式开放,编辑闸上的规则执行仍在进行中。语言只支持 TypeScript 和 JavaScript,平台只支持 Windows。我们只读了 README,没有运行这个工具,基准和变异测试的数字都来自作者自己。
给读者的实用建议
使用 Windows 且写 TypeScript 或 JavaScript 的团队,可以下载发布的二进制文件和它的 SHA-256 校验文件,先比对再用 `claude mcp add` 注册服务器。README 提醒该二进制没有代码签名,所以 SmartScreen 首次运行会警告。
从源码构建需要 Zig 0.16.0,使用 `zig build`。类型检查和测试命令由运营者提供,可通过 `--typecheck` 与 `--test`,或在使用 `--allow-repo-config` 时从 `.emetgaterc.json` 读取,模型永远不能自己提供。对其他读者,这份 README 也是一个简明的原则声明:不给模型写权限,并让每一项被接受的改动都通过人可以检查的验证。
Sources
FAQ
Emetgate 是做什么的?
它是位于语言模型与源码树之间的确定性验证内核。模型只能对某个符号提出修改,内核逐一检查,然后原子提交或附带理由拒绝。
它如何决定要不要跑测试?
内核先计算影响范围。只有排除了所有可能的逃逸途径,改动才算 BOUNDED。UNBOUNDED 的改动会在沙箱中的影子副本上运行项目的测试命令。
README 自己承认哪些局限?
通过检查的代码仍可能有错,测试闸的强度取决于测试,其他工具的编辑会绕过这道闸。沙箱目前不限制读取和网络访问。它只支持 Windows、TypeScript 和 JavaScript。