关于 cra-check
cra-check 是面向公开 GitHub 仓库的欧盟《网络韧性法案》(CRA)准备度检查服务,返回八项公开可见的安全信号及其来源。它提供参考性指标,不是合规审查。
主要功能
- 01检查八项 CRA 相关信号
- 02读取仓库文件与 GitHub 信息
- 03标明信号可见性和检查来源
- 04附带规则依据与未测范围
适用场景
- 01初步了解公开仓库的安全实践
- 02查看哪些信号未能从公开资料确认
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.tokenizen.nl/mcpclaude mcp add --transport http holistis-cra-check https://api.tokenizen.nl/mcp{"mcpServers":{"holistis-cra-check":{"type":"http","url":"https://api.tokenizen.nl/mcp"}}}在 Cursor 中打开{"servers":{"holistis-cra-check":{"type":"http","url":"https://api.tokenizen.nl/mcp"}}}在 VS Code 中打开{"mcpServers":{"holistis-cra-check":{"type":"http","url":"https://api.tokenizen.nl/mcp"}}}{"mcpServers":{"holistis-cra-check":{"serverUrl":"https://api.tokenizen.nl/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
self-check-server
tokenizen.nl/check 背后的轻量级 HTTP 后端。浏览器无法直接运行 node:dns 或 node:crypto,因此网页会调用这个轻量、真实(非模拟)的层。它不会重新实现任何所有权证明逻辑,只是通过 HTTP 接入 endpoint-attest 和 trust-attest-server 中已经过测试的函数。
为什么要将它作为独立的软件包
endpoint-attest 的 verifyOwnership() 和 trust-attest-server 的 getOwnershipChallenge() 是为 MCP 工具调用构建的,调用者是代表自身运营方操作其端点的 Agent。将相同的函数放到匿名的公开网页表单后,威胁模型就变了:任何访问者都可以提交任意 http(s) URL,让此服务对其执行真实 DNS 查询和/或 HTTP 请求。这是典型的 SSRF 攻击面,而这些函数此前从未暴露于此。此处的两项设计正是为此而设:
tokenizen-net-guard(packages/net-guard)——所有出站 DNS 或 HTTP 调用都必须经过它。主机只解析一次,所有返回的地址都会经过检查,确保不属于回环、私有、链路本地(包括云元数据地址)或保留地址范围;每一跳(包括重定向)都只会连接经过验证的地址。这种固定地址的做法弥补了 DNS 重绑定漏洞,而本软件包的第一版曾明确承认存在此问题。构建测试(net-guard/src/no-raw-egress.test.ts)会在此处的生产文件尝试直接使用原始 fetch 或 DNS 调用时失败。src/rate-limit.ts——两条路由都使用按 IP 区分的最简内存固定窗口限流器,因为此 API 按设计无需身份验证,而且每个请求都会产生真实的网络 I/O。仅适用于单进程;多实例部署则需要改用共享存储。
两条路由,与 trust-attest-server 中的免费/付费划分一致
POST /api/challenge(免费,不发起网络调用)——封装getOwnershipChallenge。请求体:{ endpoint, ownerAddress }。POST /api/verify-ownership(免费,执行一次真实 DNS/HTTP 检查)——直接封装verifyOwnership。请求体:{ endpoint, ownerAddress, method }。这仅用于所有权证明。它有意不会调用requestTrustAttestation(该付费工具也会运行检查并签署证明),因为公开页面上的免费“验证”按钮绝不能触发付费操作。将付费步骤接入真实支付(x402/mcp-paywall)是之后单独进行的改动。
默认情况下,两条路由共用 trust-attest-server 的模块级证明者身份,因此此服务签发的质询与验证所需内容相匹配——与 attester-identity.ts 中已有说明相同,身份是“临时的,会在进程重启后重置”。
免费的在线 x402 检查
POST /api/x402/check(免费,可测量一次访客自己的端点)——请求体{ url, method? },位于 tokenizen.nl/x402-check 后方。我们的服务器会向该端点发送一个匿名请求,不会付款,并返回一份带日期的判定结果:支付请求是否通过官方 x402 买方架构验证(从 Payment-Required 标头或请求体中读取的 @x402/core 的parsePaymentRequired),以及它在 agent-receipt-verify 的收据阶梯中能达到哪一级(0 至 2 级,这些级别不需要付款)。端点的原始标头和请求体绝不会返回。
这项功能反转了 src/x402-badge.ts 中的一条规则:该规则禁止实时测量,以免有人利用此服务器从我们的地址探测其他主机。徽章仍遵守这条规则。实时路由则用限制取代了“禁止”,所有限制都在 src/x402-check.ts 中:仅允许 https 和 443 端口;必须使用公共域名(不能是 IP 字面量);仅允许 GET 和 POST,并使用固定的 {} 请求体;最多允许两次重定向,且只能跳转到粘贴的主机或其 www 子域名;响应上限为 64 KB(DID 文档为 8 KB);每次测量最多发起六个出站请求,时限为 45 秒,超时后会中止套接字;同时最多进行八项检查;结果缓存两分钟;每个主机每小时最多进行 12 次实时测量,总计每小时最多 200 次;此外,每个 IP 每 10 分钟最多发起 8 次请求。除粘贴的主机外,任何主机都只会收到一个请求,且只能请求固定文件 /.well-known/did.json,不带查询参数,因为被测方可以自行选择其报价中指定的 did:web 文档。不存储任何结果;缓存仅存在于内存中,服务器也不会记录 URL。每次响应都会写入一行计数日志(路由、状态、自有客户端标志),参见 src/usage-log.ts。
使用的库是 agent-receipt-verify,版本严格为 0.2.9,这是首个会将注入的 fetch 传递给 did:web 密钥查询的已发布版本。不要使用更低版本。修复之前,密钥查询的主机由被测方选择,会使用全局 fetch,绕过上述所有防护(agent-receipt-verify#46);如果这一问题再次出现,x402-check.test.ts 就会失败。
已知限制,特此记录,因为一次对抗性审查(由我们进行,并非独立审查)发现了这些问题,而代码尚未解决:控制多个 IP 地址的人可以耗尽每小时的全局额度,之后所有访客都会收到 429,直到额度重置;对同一主机使用不同查询字符串检查 12 次,会导致该主机所有者一小时内无法再进行检查。若某个主机名将我们的 DNS 查询指向一个始终不响应的名称服务器,就会一直占用共享线程池中的一个线程;同时最多八项检查的限制可以控制这种情况,在服务环境中设置 UV_THREADPOOL_SIZE=16 可为线程池留出余量。如果位于代理后方,请设置 HOST=127.0.0.1,以免他人直接访问端口并发送自己的 X-Forwarded-For(生产服务器还配置了防火墙,仅开放 22、80 和 443 端口)。
买方检查为何不使用 x402-lint:x402-lint 只读取响应体,而 v2 卖方会将要求放在 Payment-Required 标头中,响应体则为 {}。在 2026-10-24 对我们自己的在线付费路由进行测量时,它报告了“x402Version: Invalid discriminator value”。使用的是同一套架构,只是读取位置不同。
免费的 CRA 就绪情况检查
POST /api/cra/check(免费,最多向 GitHub 发起约十九次小型只读请求,其中最多六次调用其 API)——请求正文{ repo },入口为 tokenizen.nl/cra-check。它会针对一个公开的 GitHub 仓库,返回一份带日期的八项精选信号列表,每项状态为visible、not_visible或not_measured:包含联系人地址或报告链接的安全政策(仓库根目录、.github/ 或 docs/ 中的 SECURITY.md,或仓库所有者的 .github 仓库中的 SECURITY.md)、软件物料清单、签名发布版本或来源证明、锁定文件、Dependabot 或 Renovate、CI 中的安全扫描、过去 90 天内的维护情况(默认分支上的最近一次提交),以及许可证。每项不可见的信号都会说明怎样才能让它变得可见;答复还会列出访问过的 GitHub 页面及每次访问的结果(sources:已读取、部分读取、未找到、已移动、失败),读者可以打开相同页面查看。
八项信号是什么,又不是什么。 它们并非分别对应《欧盟法规 2024/2847》附件 I 第二部分八项法律要求(第 1 至 8 点)中的一项。有些信号与这些要求相关(物料清单对应第 1 点,自动更新对应第 2 点,安全扫描对应第 3 点,披露政策和联系人对应第 5 和 6 点,签名发布版本对应第 7 点);另一些则是法规未明确列出的通用良好实践(固定依赖项、近期维护、许可证);还有若干法律要求完全未测量(是否公开已修复的漏洞、是否确实及时处理漏洞、报告流程、文档)。我们于 2026-10-28 将这些主题与官方文本进行了比较;信号的选择及其解读由我们自行决定,未经律师审核。答复本身会说明所有这些内容(rules.basis、notice、whatThisDoesNotMeasure),并标明规则集版本,以便在规则变更后,带日期的结果仍有参考意义。这只是基于公开数据的指示,并非合规检查。
塑造代码的规则:只有在所有可能显示某项信号的内容都已读取后,该信号才会标为“不可见”。 整个文件树通过一次调用获取(git 树,递归),因此会发现单体仓库中的任何位置的锁定文件或 SBOM;若 GitHub 截断了文件树,或文件树超过我们的上限,则基于文件的信号会标为“未测量”。调用失败、工作流文件仅被部分读取(最多十二个,优先读取发布、发布流程和扫描文件,每个最多读取 64 KB),或 SECURITY.md 仅被部分检查(超过 16 KB、某行超过 1000 个字符,或超出读取上限),也会标为“未测量”。无法读取的仓库会返回 502,且不会缓存;包含未读取部分的报告会缓存五分钟,而非一小时。只有看起来属于报告渠道的链接(security、advisories、report、disclosure、漏洞赏金平台、contact)才算作联系人链接,而非项目主页。“不可见”绝不会表述为“缺失”:在单独的构建服务器或注册表中完成的签名或 SBOM,以及在仓库设置中启用的扫描或 Dependabot 警报,都无法从仓库中看到。这一做法源于外部的第二意见及我们内部的两次审查(2026 年 10 月 28 日);cra-repo-check.test.ts 涵盖了所有这些情况。
所有防滥用措施都在 src/cra-check.ts 和 src/cra-repo-check.ts 中:访客只能选择一对 owner/repo,不能选择主机;在构建任何地址之前,必须先通过严格的格式校验(每个路径段都会再次进行百分号编码,且文件从 HEAD 读取,因此不会有仓库所有者选择的文本(例如分支名称)进入地址);唯一会被请求的主机是 api.github.com 和 raw.githubusercontent.com,所有请求都通过 net-guard 的 guardedFetch 发出,且不会跟随重定向;每个响应的读取都有字节数上限(文件树为 3 MB,其他 API 响应为 1 MB,每个原始文件为 200 KB),因为仓库所有者可以决定文件有多大;策略文本会截断至 16 KB,并逐行使用有界模式进行匹配;工作流文本上限为 64 KB;同时最多处理六项检查;每项检查的截止时间为 30 秒,超时会中止套接字;每个仓库缓存一小时(仓库不会每分钟都变),「未找到」结果缓存五分钟;整个服务每小时设有配额(无令牌时为 5 次,有令牌时为 250 次;不存在的名称会返还配额,并使用单独的配额池,该池容量为其四倍),此外,每个 IP 每 10 分钟最多请求 6 次;来自仓库的文本会截断并清除控制字符,页面会将其作为文本呈现。报告不会存储:答案会在服务器内存中保留最多一小时,本服务也不会记录任何仓库名称。
GitHub 配额。 每项检查最多需要调用 6 次 API(仓库、文件树、发布版本、安全公告、最近一次提交,以及仓库自身没有策略时所有者的 .github 树);原始文件读取不计入 GitHub 配额。没有令牌时,整个服务每小时只有 60 次调用的配额,一个访客就可能耗尽所有人的配额,因此除非设置了 CRA_CHECK_GITHUB_TOKEN,或设置 CRA_CHECK_ALLOW_ANONYMOUS=1 表示允许匿名使用,否则该路由会被关闭(503)。将 CRA_CHECK_GITHUB_TOKEN 设为仅授予零权限(公开仓库,只读)的细粒度令牌,且该令牌仅供本服务使用;这样配额会提高到 5000,同时为同一令牌的其他用途留出余量(默认设置最多约使用一半配额),且令牌只会发送给 api.github.com。切勿使用个人令牌。当 GitHub 提示「请放慢速度」时,响应为 429,且不会缓存任何内容。
已知且无法通过代码消除的限制:控制大量 IP 地址的人(使用令牌时约 40 个)可以在一小时内耗尽新名称的配额,之后每个新名称都会收到 429,直到配额重置;已在缓存中的仓库仍会正常响应。CORS 只决定哪些浏览器页面可以读取响应,无法阻止其他调用方。重命名后的仓库会返回「已移动」,必须使用当前名称重新查询。若策略、SBOM 或签名步骤位于公开仓库和 GitHub 发布版本之外,就无法看到。不会读取根目录以下的 package.json 中的 Renovate 设置。许可证信号只接受 GitHub 自身的检测结果,或根目录中的 LICENSE、COPYING 或 UNLICENSE 文件,不会读取文件内容。
此路由经过一次外部独立评审,以及两次针对我们自身的对抗性评审(2026 年 10 月 28 日)。第二次评审发现,SECURITY.md 中一行长达 200 KB 的文本可能让正则表达式持续运行约 20 秒,从而阻塞所有路由;现在的测试会对抗恶意输入计时。尚未进行独立的外部评审。
用法
npm install # from the monorepo root
npm run build -w self-check-server -w endpoint-attest -w trust-attest-server
PORT=8787 npm run start -w self-check-server
# or, for local development with auto-reload:
npm run dev -w self-check-server
使用环境变量进行配置:PORT(默认值为 8787)、ALLOWED_ORIGINS(以逗号分隔,默认包含 http://localhost:5173 和生产环境的 tokenizen.nl 域名)。
测试
npm test -w self-check-server — 所有测试均针对一个真实的 Express 应用运行,该应用使用真实的(环回、临时端口)套接字;每个 DNS 查询和 HTTP 请求都会通过注入的假对象替代,使用的正是 ownership.ts/checks.ts 已采用的依赖注入接口。本软件包中的测试不会执行真实的 DNS 查询或出站 HTTP 请求,包括 x402 检查测试,它们会用一个模拟 net-guard 的假对象替代出站请求。
状态
已在 https://api.tokenizen.nl 上线:前置 Caddy,此服务由其专属的非特权用户运行在后方,手动部署(以服务用户身份拉取、构建并重启单元)。所有权路由和付费路由上线较早;x402 检查则是新功能。x402 检查上线前经过了内部对抗性审查。这是我们自己的审查,并非独立审查;本软件包尚未接受独立的外部审查。
将 CRA 检查作为 MCP 工具
同一项检查也是位于 POST /mcp(src/cra-mcp.ts)的 MCP 工具,因此 AI Agent 无需浏览器即可调用。它是一个只读工具 check_github_repo,接收 repo(owner/name 或其 github.com 地址),并返回与 POST /api/cra/check 相同的带日期报告,或返回普通错误。它使用同一个检查器,因此网页背后的限制同样适用:严格的 owner/repo 格式、一个小时的缓存、同时最多执行少量检查,以及 GitHub 的每小时预算。
无状态:每个请求都会创建自己的服务和传输,且请求之间不保留任何内容。只允许 POST(GET 和 DELETE 会返回 405),并且拒绝批量消息,因为批量请求可能在一次请求中包含许多工具调用。调用工具的请求使用与网页路由相同的限流器(每个地址每十分钟六次);连接、列出工具和 ping 则使用更宽松的限流器(每十分钟六十次),这样 Agent 可以先查看,而不会耗尽检查次数。响应会像其他免费检查一样计数(src/usage-log.ts:路由 mcp、状态、自有客户端标记;不记录地址和仓库名称)。
使用默认代理模式下的 MCP Inspector 进行试用:npx @modelcontextprotocol/inspector,传输方式选择 Streamable HTTP,URL 填写 https://api.tokenizen.nl/mcp(不允许其直接从浏览器连接的模式,因为该模式不在 CORS 列表中)。超过限额的工具调用会以带有 isError 和等待秒数的工具结果作为响应,而不是直接返回 HTTP 429。只有工具调用会计为使用。此文件夹中的 server.json 是官方 MCP 注册表的条目。
无需联系本站即可验证 Signal 日志
website/public/verify-cra-log.mjs(通过 https://tokenizen.nl/verify-cra-log.mjs 提供)仅凭文本检查日志:单个文件、无需安装、无需联网。运行 node verify-cra-log.mjs yourlog.jsonl 会打印 ok 和链头;如果有问题,则打印 NOT OK at line N 及原因。它与 src/cra-log.ts 中的 verifyChain 执行相同的检查;src/cra-log-standalone.test.ts 会用一份正确日志和十四种损坏日志分别运行两者,并在结果不一致时失败。它能证明这些行属于同一条链且未被更改;日志写入时间则由链头的时间戳显示(OpenTimestamps,ots verify)。
在服务器上手动运行每周任务
bench/cra-weekly.sh repos.txt logdir 会为 repos.txt 中的每个仓库添加一行,写入所有日志的链头,并使用 OpenTimestamps 为该文件加上时间戳;几小时后,运行 ots upgrade <file>.ots 即可确认时间戳。Linux 服务器上的客户端位于 /opt/ots(一个 Python 虚拟环境;删除该文件夹即可撤销安装):OTS=/opt/ots/bin/ots bench/cra-weekly.sh ...。客户端无法在 Windows 上启动(缺少一个库),因此应在服务器上运行。日志包含客户数据(仓库名称):请将日志文件夹放在仓库之外,并避免将其纳入离开服务器的备份。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。