PAYATHON 2026

GitLab CI 中 Lerna 发布 npm 私有包为何返回 403?

数据小许

结论

日志显示,Lerna 在发布前验证 npm 凭据时,请求 https://registry.npmjs.org/-/npm/v1/user,收到 403。可以确认失败发生在凭据验证阶段,但仅凭这段日志无法判断原因:可能是 token 没有传入、权限或有效期不符合要求,也可能是当前 npm/Lerna 使用的验证流程与 token 不匹配。

日志中的安全提示称,classic tokens 已撤销,granular tokens 最长有效期为 90 天,且默认要求 2FA。发布 job 实际写入 .npmrc 的是 NPM_AUTH_TOKEN。配置 id_tokens 本身并不代表发布命令使用了 OIDC。

排查步骤

1. 确认 CI 中有可用的 token

写入 .npmrc 前检查变量是否非空,但不要打印 token 内容:

- test -n "$NPM_AUTH_TOKEN" || (echo "NPM_AUTH_TOKEN is empty or unavailable" && exit 1)
- printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_AUTH_TOKEN" > ~/.npmrc

确认 GitLab CI/CD 变量的可用范围涵盖当前分支、tag 和发布 job。如果变量仅对受保护分支开放,而流水线运行在未受保护的 ref 上,job 可能无法取得该变量。

2. 核对 token 设置

在 npm 中确认 granular access token 仍然有效、尚未过期,并且拥有目标包所需的发布权限。如果包属于某个组织或使用 scope,还要确认 token 对应的账户和授权范围涵盖该包。日志提到默认要求 2FA;请根据 npm 中该 token 的实际设置,确认是否需要配置相应的 2FA 处理方式。

不要把 token 写入仓库文件或 CI 日志。发布 job 只需在运行时将它写入 npm 配置。

3. 在同一 job 中单独验证 npm 凭据

在相同的 runner 和环境变量下,使用同一份 .npmrc 执行:

- npm whoami --registry=https://registry.npmjs.org/

如果这一步也失败,问题仍在 CI 中的 token 注入、授权或 npm 凭据验证流程,尚未进入 Lerna 发布包的步骤。如果 npm whoami 成功,但 Lerna 仍在 /-/npm/v1/user 返回 403,继续核对当前 npm 和 Lerna 的版本,以及它们的凭据验证行为。现有材料无法确定需要升级到哪个版本。

4. 核对 Lerna 使用的配置

当前 job 会在运行 lerna publish from-package 前直接重写 ~/.npmrc。确认 .npmConfig 脚本是否依赖这份文件中的其他必要配置,例如 registry 或 scope 设置。如果有,将这些配置与 token 配置合并写入同一份临时 .npmrc,避免覆盖后丢失。

示例中的 OIDC token 声明只有在发布流程实际采用 OIDC 身份验证时才会生效。既然 Runner 不支持 OIDC,应先按 granular token 路径排查。只有 Runner 和发布配置能够完成 OIDC 身份交换时,再考虑改用这种方式。

注意事项

403 出现在 Lerna 验证 npm 凭据的请求中。先确认 CI 能否使用该 token 通过 npm 凭据验证,再排查包级发布权限。npm whoami 成功也不能单独证明 token 有权发布目标私有包。诊断输出中不要打印 token。

备注:内容仅供参考。