npm 分阶段发布:让包版本上线前必须经过人工批准

2026-08-07 63 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

过去,npm publish 往往同时意味着上传完成和版本立即可安装。npm 新增的分阶段发布改变了这条路径:版本先进入待发布队列,只有维护者完成人工批准并通过双因素认证挑战后,才会正式对用户开放。这为自动化流水线与公开发布之间增加了一道明确的安全边界。

发布动作被拆成两个阶段

分阶段发布把一次高风险操作拆成两个职责不同的步骤:

  1. CI 或维护者构建、检查并上传包版本。
  2. 具备权限的维护者核对版本,再通过 2FA 批准上线。

进入队列并不等于已经发布。处于待批准状态的版本还不能通过常规的 npm install 安装,因此,即使攻击者控制了发布流水线,也不能仅凭一次自动化任务把恶意版本直接送到用户环境。

这项能力针对的是发布链路中的关键风险:被盗用的令牌、遭篡改的 CI 配置、维护者工作站失陷,以及自动化任务错误地发布了不应公开的构建产物。人工批准不是对代码审查的替代,而是部署前的最后一道确认。

使用该功能需要满足版本要求:npm CLI 11.15.0 或更高版本,以及 Node.js 22.14.0 或更高版本。npm 同时提供了新的可配置权限标志,团队应根据包、组织和发布账户的职责设置最小权限,而不是让所有维护者共享同一组高权限凭据。

把版本检查和制品检查放进发布脚本

启用分阶段发布前,可以先固定本地与 CI 的工具版本,并在上传前检查 tarball。下面的脚本可以直接用于项目;运行前把 EXPECTED_PACKAGE 改成 package.json 中的实际包名。

#!/usr/bin/env bash
set -euo pipefail

EXPECTED_PACKAGE="@example/my-package"

node -e '
const [major, minor] = process.versions.node.split(".").map(Number);
if (major < 22 || (major === 22 && minor < 14)) {
  throw new Error(`Node.js 22.14.0+ required, found ${process.versions.node}`);
}
'

npm_version="$(npm --version)"
node -e '
const [major, minor] = process.argv[1].split(".").map(Number);
if (major < 11 || (major === 11 && minor < 15)) {
  throw new Error(`npm 11.15.0+ required, found ${process.argv[1]}`);
}
' "$npm_version"

package_name="$(node -p 'require("./package.json").name')"
if [ "$package_name" != "$EXPECTED_PACKAGE" ]; then
  printf 'Unexpected package name: %s\n' "$package_name" >&2
  exit 1
fi

npm test
npm pack --dry-run
npm publish

这里的 npm publish 仍然是上传入口。分阶段发布是否生效,应由包或组织的 npm 发布配置决定;启用后,命令成功不代表该版本已可安装,而是意味着维护者还需要处理待批准版本。具体配置入口和权限名称可能随 npm 产品界面调整,应以账户中显示的选项为准。

npm pack --dry-run 尤其值得保留。它能在发布前列出将进入包中的文件,帮助发现意外打包的 .env、测试数据、私钥、内部配置或体积异常的构建目录。

可以这样接入 CI

下面是一个可改造的 GitHub Actions 示例。它假设仓库已经配置名为 NPM_TOKEN 的发布凭据,并且 npm 侧已经为目标包启用分阶段发布。工作流负责测试和上传,人工批准仍在 npm 的待发布队列中完成。

name: Publish package

on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22.14.0
          registry-url: https://registry.npmjs.org
          cache: npm

      - name: Install supported npm CLI
        run: npm install --global npm@11.15.0

      - name: Install and test
        run: |
          npm ci
          npm test
          npm pack --dry-run

      - name: Queue package version
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

运行前需要修改触发条件、Node 版本和凭据方案,使其符合团队策略。若团队采用 npm 支持的无长期令牌认证方式,也应优先评估该方式,减少静态发布令牌泄露后的影响范围。

这条流水线刻意不尝试在 CI 中完成 2FA 批准。把批准凭据重新塞回自动化系统,会削弱人工关卡的价值。更合理的职责划分是:CI 产生可审计的候选版本,维护者查看版本号、提交、测试结果和包内容,再独立批准。

批准时应该检查什么

人工步骤只有在检查内容明确时才有价值。维护者可以围绕下面几项做快速核对:

  • 版本号与发布计划一致,没有错误升级或覆盖发布意图。
  • Git 提交、标签和 CI 构建记录能够互相对应。
  • 测试、类型检查和安全扫描已经通过。
  • npm pack --dry-run 的文件列表没有凭据、内部文件或异常制品。
  • package.json 中的入口、脚本、依赖和发布配置符合预期。
  • 批准操作由独立设备或可信认证器完成 2FA,而不是共享一次性验证码。

批准后,还应执行一次外部验证。把下面的包名和版本替换为刚刚上线的版本:

PACKAGE="@example/my-package"
VERSION="1.2.3"

npm view "${PACKAGE}@${VERSION}" name version dist.integrity
npm install --ignore-scripts "${PACKAGE}@${VERSION}"

--ignore-scripts 适合用于初步验证陌生或高风险包,避免安装阶段立即执行生命周期脚本。它不能替代正常的集成测试;确认制品身份后,还应在隔离环境中运行真实安装和功能检查。

采用时不要忽略这些边界

分阶段发布能阻止单次自动化失陷直接变成公开版本,但不能修复恶意源码、被污染的依赖、维护者账号整体失陷,或审批者未经核对就点击批准的问题。它也会增加发布时间,尤其是在跨时区团队和紧急修复场景中。

落地时建议先选择一个低风险包试运行,记录上传、通知、批准、验证和撤回的责任人及响应时间。随后再逐步覆盖关键包,并同步收紧发布权限、启用独立 2FA、保护发布分支、保留制品清单和构建来源记录。真正有效的供应链防护来自多层控制;分阶段发布的价值,在于把“上传成功”和“用户可安装”变成两个可以分别审计的安全事件。


相关推荐