欢迎光临
我们一直在努力

OpenClaw 的 278 个版本里,有 7 个是绕过 CI 从个人机器直发的:缺 SLSA 溯源,还把维护者的本地路径写进了公开元数据

一、先说结论:你装的每一版 OpenClaw,npm 都会在你机器上执行两段代码

装 OpenClaw 的那一刻,npm 会自动在你的机器上跑两段代码,这件事在 npm 官方文档里有明确定义,和 OpenClaw 自己的文档没有任何关系。

npm 官方把这类脚本定义为生命周期脚本(life cycle scripts),并明确规定了它们的触发时机。真正会在你 npm install 的时候、在你自己的机器上、带着你自己的权限执行的只有三个:preinstall、install、postinstall。npm 官方文档在这三者之前还额外列了 prepare,但文档同时写明 prepare 在 npm install 场景下只在本地包、无 node_modules 的情况下才跑,对已发布的包不生效。所以对「装一个已发布的 OpenClaw」这个动作而言,实际执行的消费端钩子是三个而不是四个。

按 npm 官方文档给出的顺序,npm install 的钩子执行次序是:preinstall → install → postinstall → prepublish → prepare → prepare(重复一次)→ postprepare。文档原文写的是 “These all run after the actual installation of modules into node_modules, in order, with no internal actions happening in between”——即这几个钩子按序执行,中间不会插入 npm 的其他内部动作。

npm 官方文档:life cycle 操作顺序,preinstall / install / postinstall 在安装时按序执行

那么 OpenClaw 声明了哪几个?从 npm registry 的 package.json 元数据看,最新稳定版 2026.10.4 声明了 两个,而不是三个:

钩子 是否声明 声明的命令 归属
preinstall 是 node scripts/preinstall-package-manager-warning.mjs 消费端
install 否 — —
postinstall 是 node scripts/postinstall-bundled-plugins.mjs 消费端

也就是说,install 这个环节是空的,剩下两个都是 node 脚本。全文检索 278 个版本的元数据,install 钩子一次都没有出现过(0/278),这是本次全量扫描中唯一一个「始终为零」的消费端钩子。

这两个脚本具体做了什么,属于包元数据无法回答的部分——脚本内容不在 package.json 里,也不在 registry 的 tarball 索引里。本文只声明「这两个钩子存在,且会执行哪条命令」,不声明它们的行为。

二、全量扫描:195 个版本有 preinstall,222 个有 postinstall,而 install 永远是 0

本文的数据基础是 npm registry 上 openclaw 这个包的全部 278 个版本的元数据,一次性抓取、全量解析,没有抽样。抓取时间是 2026-10-18(Asia/Shanghai 下午),响应体约 19.9 MB。

按消费端钩子组合统计,278 个版本分成三组:

钩子组合 版本数 占比 覆盖版本区间
preinstall + postinstall 195 70.1% 2026.4.15-beta.1 ~ 2026.10.4
仅 postinstall 27 9.7% 2026.1.29-beta.1 ~ 2026.4.14
三者全无 56 20.1% 0.0.1 ~ 2026.3.28

消费端钩子的时间线本身有两段:

  • 2026-01-30 起(2026.1.29-beta.1)第一次出现 postinstall,命令是 node scripts/postinstall.js。这一段只有 postinstall,持续了 4 天。
  • 2026-02-04 至 2026-03-29 这 55 天是一个完全没有任何消费端钩子的窗口,共 56 个版本。
  • 2026-03-31(2026.3.31-beta.1)postinstall 回来了,但命令换成了 node scripts/postinstall-bundled-plugins.mjs,和第一次的 postinstall.js 不是同一个脚本名。
  • 2026-04-15(2026.4.15-beta.1)起 preinstall 才第一次加入,从此 195 个版本稳定保持 preinstall + postinstall 的组合,直到最新的 2026.10.4。

值得注意的是 postinstall 的命令在历史上换过一次:从 node scripts/postinstall.js 改成 node scripts/postinstall-bundled-plugins.mjs。包元数据只能证明「脚本名换过」,换的动机没有公开说明,记为 UNKNOWN。

另外一个容易被忽略的细节:发布端钩子(prepare、prepack、postpack)和消费端钩子完全是两套生命周期。prepare 在 220 个版本中出现过(79.1%),prepack 在 229 个版本中出现过(82.4%),postpack 在 33 个版本中出现过(11.9%),但这些都只对从源码安装(npm install git+https://...)有意义,对已发布包不生效。而且最新的 2026.10.4 上 prepare、prepack、postpack 全部不存在——该版本共存 585 个 scripts,其中真正影响用户安装的仍然只有那两个。

三、发布身份:从个人账号到 CI,但不是一次性切换的

把 278 个版本的 _npmUser 字段(发布账号)拉出来统计,得到的是一个双发布者结构:

发布者 版本数 占比
GitHub Actions 211 75.9%
steipete(个人账号) 67 24.1%

时间线上,第一个由 CI 发布的版本是 2026.3.24-beta.1(2026-03-25T12:19:20Z),距包的首个版本 0.0.1(2026-01-29)55 天。此后 CI 逐步接管,但个人账号从未彻底退场——它又断断续续回来了 7 次。

这里有一个容易被忽略的机制细节。npm 从 2025 年起把「可信发布者」(trusted publishing)作为 OIDC 的一种形式引入,CI 发布者会带一个 trustedPublisher 字段。全量扫描的结果是:

  • 211 个 CI 发布版本,100% 都带 trustedPublisher,内容为 {"id":"github","oidcConfigId":"76348ccd-760... 这类结构。
  • 67 个个人账号发布版本,0 个带 trustedPublisher。

也就是说,字段本身就足以区分两类发布,不需要额外推断。

四、核心发现:CI 接管之后,仍有 7 个版本从个人机器直发,全部没有溯源证明

这是本次扫描最重要的结论。把「发布时间晚于首次 CI 发布」且「发布者是个人账号」两个条件取交集,得到 7 个版本:

版本 发布时间(UTC) 消费端钩子 溯源证明
2026.4.5 2026-04-06T03:34:53 仅 postinstall 无
2026.4.7-1 2026-04-08T04:24:00 仅 postinstall 无
2026.4.8 2026-04-08T05:18:48 仅 postinstall 无
2026.5.5-beta.2 2026-05-06T07:26:28 preinstall + postinstall 无
2026.7.1-1 2026-07-18T03:25:50 preinstall + postinstall 无
2026.7.1-2 2026-07-18T03:53:48 preinstall + postinstall 无
2026.8.1-beta.2 2026-08-15T05:35:31 preinstall + postinstall 无

这 7 个版本有两个共同特征,且这两个特征都不是巧合:

特征一:全部没有 SLSA 溯源证明。 211 个 CI 版本 100% 带 dist.attestations(predicateType 为 https://slsa.dev/provenance/v1),而这 7 个全部没有。这个字段不是元数据里的自我声明——我直接请求了 npm 的溯源端点做交叉验证:

请求的版本 溯源端点响应
openclaw@2026.10.4(CI 发布) HTTP 200,15,994 字节
openclaw@2026.3.28(CI 发布) HTTP 200,14,495 字节
openclaw@2026.4.8(个人直发) HTTP 404
openclaw@2026.7.1-2(个人直发) HTTP 404

CI 发布的版本能取到真实的溯源证明;个人直发的版本端点直接 404。这不是元数据的自说自话,而是第三方可独立复核的证据。

特征二:全部带上了构建机的本地目录路径。 npm 会在元数据里保留一个 _from 字段记录包的来源目录。这 7 个版本里的 6 个写的是维护者本机的绝对路径:

  • 2026.7.1-1 和 2026.7.1-2 的 _from 是 file:/Users/steipete/clawdbot/.worktrees/release-2026.7.1-hotfix/.artifacts/correction-2026.7.1-1/tarballs/...——包含用户名、仓库旧名、worktree 分支名和 hotfix 目录结构。
  • 2026.4.5 是 file:/tmp/openclaw-npm-preflight-v2026.4.5/...;2026.8.1-beta.2 是 file:/tmp/openclaw-core-preflight-31865167546/...(目录名带一个数字串,像构建流水号)。

作为对照,203 个 CI 发布的版本里,_from 100% 都是归一化形式 file:preflight-tarball/openclaw-.tgz,0 个例外;而 CI 版本中另外 8 个干脆没有这个字段。所以「个人直发」和「路径未归一化」在这份数据里是同一个现象的两面。

顺带一个细节:那条路径里的 clawdbot 是该项目在 registry 上的包名变成 openclaw 之前的历史名称,全量扫描中 59 个早期版本内部仍带有这个字符串。需要说明的是,registry 的 repository 字段从 2026.2.17 起就固定为 git+https://github.com/openclaw/openclaw.git,从未变过,所以这是代码库目录名残留,不是包名或仓库地址变更。

五、这 7 个版本有一个共同点:它们从来没成为任何一个 dist-tag

如果上面那 7 个只是「CI 偶尔掉线时的兜底」,那问题就小得多。但全量核对 dist-tag 指向后,事实更微妙:

  • 这 7 个版本没有任何一个曾经是 latest、extended-stable 或 beta 标签的指向目标。
  • 反过来,最近一次个人账号发布是 2026.8.1-beta.2(2026-08-15),距今 61 天。
  • 而 2026.8.1-beta.3(9 天后)就回到了 CI 发布,之后连续 32 个版本由 CI 独占,直到最新的 2026.10.4。

也就是说,从用户实际会装到的版本这个角度看,风险敞口比 7/278 这个比例看起来要小得多:一个坚持按 dist-tag 安装、且从不指定具体版本的普通用户,在过去 61 天里不会碰到这些直发版本。

但这个缓解不能抵消问题,原因是这些版本的元数据是公开的,且路径信息是永久可查的。任何人都可以在 registry 上读到 2026.7.1-1 的 _from 里包含维护者的 macOS 用户目录,而 registry 的版本一旦发布就不可删除。

六、证据口径:哪些是实测,哪些不能从元数据得出

本文所有数字均来自 npm registry 公开元数据的全量解析,加上对 npm 溯源端点的直接 HTTP 请求。为避免越界推断,明确区分三层:

第一层,可直接读到的字段事实(本文绝大多数结论属于此层):钩子是否存在、命令字符串原文、发布账号、dist.attestations 有无、_from 路径原文、trustedPublisher 有无。这些都是 registry 返回的原始值。

第二层,引用官方文档的机制解释:生命周期钩子的触发时机与执行顺序、provenance 的含义与限制。这些结论来自 npm 官方文档,不是我实测得到的。

第三层,明确标记为 UNKNOWN 的部分:

  1. preinstall-package-manager-warning.mjs 与 postinstall-bundled-plugins.mjs 两个脚本具体执行了什么行为,未实测——脚本内容不在包元数据内,我没有解包也没有运行。
  2. 这 7 个直发版本的构建环境与 CI 是否一致(Node 版本、依赖树、测试是否通过)是否等价,未验证。包元数据里其实有个 _nodeVersion 字段记录发布时的 Node 版本,但它是 npm 客户端内部字段,在 CI 发布和直发版本上都存在,只是语义未公开说明,记为 UNKNOWN。
  3. 这 7 个版本为何绕过 CI,原因无任何公开说明,记为 UNKNOWN。
  4. postinstall 脚本名从 postinstall.js 换成 postinstall-bundled-plugins.mjs 的动机,无公开说明,记为 UNKNOWN。
  5. prepare 在 npm install 场景下「只对本地包生效」的边界条件,引用自官方文档行为描述,未在本机实测。
  6. 注册表中的 _from、_nodeVersion 等下划线前缀字段属于 npm 客户端内部字段,其长期保留策略与是否会被清理,无公开说明,记为 UNKNOWN。

七、如果你要在生产环境用 OpenClaw,这几条是可以直接执行的

上面的分析最终要落到可执行动作上。以下建议均基于本文已核实的数据,不涉及未验证的行为推断:

  • 别把「有 integrity 哈希」当成供应链安全。 278 个版本全部有 dist.integrity 和 dist.signatures,包括那 7 个直发版本。哈希只能证明「下载的内容和发布的字节一致」,证明不了「这些字节是怎么产生的」。区分二者要看 dist.attestations 的有无。
  • 判断一个版本是否可信,看 dist.attestations 而不是看版本号大小或 beta 后缀。 判断方法:请求 https://registry.npmjs.org/-/npm/v1/attestations/openclaw@,返回 200 有证明,返回 404 就是没有。本文第四节表格里的 4 次实测结果可直接作为判定方法的示范。
  • 在 CI 里固定按 dist-tag 安装,不要钉死具体版本号。 理由见第五节:7 个直发版本从未成为任何 dist-tag。
  • 企业环境可以考虑 --ignore-scripts。 这是 npm 官方提供的开关,会跳过生命周期钩子。但请注意,本文未实测 OpenClaw 在该开关下能否正常运行——它大概率会跳过 bundled plugins 的初始化步骤,本文记为 UNKNOWN。

八、相关阅读

npm 官方文档:provenance 的两类证明,以及「有 provenance 不代表包不是恶意的」这一限制
OpenClaw 仓库的 package.json 原文页面

九、本站其他相关文章与分工

为避免与站内已有内容重复,本文专门选择了此前未被开采的角度——安装期执行面与发布身份,而不是版本台账、包体积、模块面或依赖锁定。相关已有文章分工如下:

十、来源与采集口径

赞(0)
未经允许不得转载:时间网 » OpenClaw 的 278 个版本里,有 7 个是绕过 CI 从个人机器直发的:缺 SLSA 溯源,还把维护者的本地路径写进了公开元数据

更好的WordPress主题

支持快讯、专题、百度收录推送、人机验证、多级分类筛选器,适用于垂直站点、科技博客、个人站,扁平化设计、简洁白色、超多功能配置、会员中心、直达链接、文章图片弹窗、自动缩略图等...

联系我们联系我们

登录

找回密码

注册