是哪份代码检查了你:suite_commit 与 suite_digest
本文译自 2026-09-24 的英文原文。
(原文写于 2026-09-24;之后公开包从六个增加到七个:2026-10-06 新增 toolset-diff(逐个工具比较两份工具列表),部署脚本第 3 条要求树相同的「全部公开包」也随之变为七个;另外,自 2026-10-05 起,镜像快照还会带上签名公钥文件的一份副本(
keys/mcpcheckup-keys.json)。)
本站的签名结果说明检查对一台 server 观测到了什么。它还得说明是哪些检查。检查会变:这个月(2026 年 9 月),error_taxonomy 检查学会了记录七种不属于规范错误的答复,版本从 0.4.0 升到 0.5.0。上周签名的结果和今天签名的结果出自不同的代码,比较两者的读者需要从结果本身得知这一点,而不是去翻我们的更新日志。
所以从 0.3 版起,每个签名 payload 都带着两个字段。
suite_commit是公开仓库 github.com/mcpcheckup/checks 里的一个 commit。suite_digest是该 commit 下packages/checks目录的git ls-tree输出的 SHA-512,以 base64 编码。
自 payload 0.3 起,任何签名结果的证据页都会印出这两个值,并印出由前者复算后者的命令:
git ls-tree -r -z --full-tree <suite_commit> -- packages/checks | openssl dgst -sha512 -binary | openssl base64 -A
在输出前面加上 sha512- 再比对。这条命令只需要一个检出到该 commit 的克隆、git 和 openssl,别的都不需要:不用安装,不用构建,除了克隆之外不用联网。
为什么用目录树清单,而不是一个包
git ls-tree -r 会为 packages/checks 下的每个文件打印它的模式、类型、blob 哈希和路径。对这份清单取哈希,就确定了检查套件里每个源文件的确切字节;而且它只读已提交的对象,所以结果不可能取决于工作区、操作系统、换行符,或装的是哪个版本的 Node。两个人在同一个 commit 上得到的清单逐字节相同。-z 这个参数很要紧:它让路径名保持原始字节,不加引号转义,所以即使某个路径不是合法的 UTF-8,参与哈希的也是它本身。
我们第一次实现这个字段时不是这样做的。在 payload 0.1 和 0.2 版里,suite_digest 的定义是检查套件已发布的 npm 包的完整性哈希。可那个包从来没有发布过。这个值在 2026-08-22 算过一次,贴进探测器的配置文件,然后就一直留在那里,而检查在它底下改了不止一次。按这两个版本签名的结果,按签名时的原样显示摘要,证据页也直说:它指向的不是任何人能取到的目录树,无法复算。我们不会重新签它们。
怎样防止探测器签上过期的值
手贴那个值给我们的教训是:靠人手维护的身份标识会腐烂。现在的值从不写进文件。它在部署时由被部署的 commit 算出,作为两个变量传给 Worker;做这件事的脚本是部署探测器唯一受支持的方式。
在运行任何东西之前,这个脚本要求四件事同时成立,否则拒绝执行:
- 工作区是干净的:没有改动过的已跟踪文件,也没有未跟踪文件。被忽略的文件不在检查之列,但 Worker 是从
HEAD的git archive导出构建的,所以它们永远进不了 Worker。 HEAD是刚刚拉取的私有仓库main的最新提交。- 公开仓库的
main上有一个 commit,它的公开包与HEAD的目录树完全相同。是全部六个公开包,不只是摘要覆盖的那一个。 - 那个公开 commit 的摘要等于
HEAD的摘要。
然后它把那个 commit 用 git archive 导出到一个临时目录,从那里构建,而不是从检出的工作区构建;这样,一个被已跟踪文件引用的被忽略文件,就不可能被打进包里,而签名里的身份却指向一个从未包含它的 commit。探测器自己的配置文件里,这两个变量刻意填的是无效的占位值。如果有人绕过脚本部署,探测器会发现这些占位值,干脆拒绝探测,而不是把它们签进结果。
同一个脚本在 CI 里对 main 的每个 commit 都会运行,并把结果作为以该 commit 命名的构建产物上传,摘要旁边附上清单的条目数和字节数。每次部署前,执行部署的人会把脚本打印的值和那个产物比对;我们的部署流程规定,两者不一致就不部署。这个比对是流程中的一步,不是脚本强制执行的。
哪些是公开的,公开仓库是什么
私有仓库里放着网站、探测器和数据库迁移。其中六个包是公开的:检查套件、它的测试夹具(fixtures)、规范化器(canonicalizer)、证明 schema、验证脚本和 SSRF 防护。一个镜像脚本把恰好这六个目录原样(目录树相同)复制到公开仓库;只有评审同意后,才会推送一个带标签的快照。标签形如 snapshot-2026-09-23-a31d17a88c5a。如果一个快照改变了检查套件的目录树,从而改变了摘要,它还会有一个 GitHub Release,说明里写明版本、改了什么,以及新旧两个摘要。第一个这样的 Release 于 2026-09-23 随检查套件 0.5.0 发布。只改了 README 的快照没有 Release。
公开仓库里还有一个 SECURITY.md,提供私下报告漏洞的渠道;以及一个 CONTRIBUTING.md,说明我们接受和不接受哪些贡献。
这些不能给你什么
它告诉你跑的是哪份检查套件代码:那个 commit 下的 packages/checks 目录树。运行它的探测器,以及和它一起安装的包,都不在摘要范围内。它本身也不能让你验证签名。那是另一项核对:公开仓库里的验证脚本,用我们发布在 /.well-known/mcpcheckup-keys.json 的公钥核对签名,证据页印出了做这件事的两条命令。
它也不能告诉你代码是对的。摘要表明,跑的检查套件代码就是那个 commit 下的 packages/checks 目录树。这份代码有没有得出正确的结论,要由代码在公开场合自己回答,这正是它公开的原因。
事实从哪里来
复算命令是在一台装有 git 2.34.1、无法访问我们部署环境的 Linux 机器上,针对上面链接的证据页所写的 commit 运行的,输出与该页的 suite_digest 逐字节一致。标签名、Release 及其日期都在公开仓库上。部署脚本的四项拒绝条件和导出步骤在私有仓库的 scripts 目录里;每一项拒绝都有一个测试,让脚本对着一个用完即弃的仓库运行,并断言 wrangler 从未被调用。