chore(ci): Add --provenance to npm publish and id-token permission - #9532
Conversation
|
Re-run after the test-pin fix (commit Template looks good ✓ Problem: real, and verifiable. Not a bug claim, so no reproduction to ask for — a factual gap: all six publish sites in the release pipeline ( Direction: aligned. Supply-chain provenance is where npm itself is heading, and this is the correct first half of the Trusted Publishing migration, with token removal sensibly deferred to a follow-up so there's no publishing gap. The prior run held the merge decision for maintainer sign-off because this touches the release pipeline — that sign-off is structurally enforced regardless: Size: not applicable — no core package paths; 4 files, +10/−7 (three workflow YAMLs + one test file). Approach: minimal, correctly scoped. Every edit is the same mechanical change — insert Risk: no revert-history high-risk path matches (workflow YAML + one test). The release-pipeline sensitivity is covered by the code-owner requirement above. Moving on to code review. 🔍 中文说明测试固定修复后(commit 模板完整 ✓ 问题:真实、可验证。 不是 bug 修复,没有需要索要的复现——而是事实性缺口:发布流水线中全部 6 处 publish 点( 方向:对齐。 供应链 provenance 正是 npm 自身推进的方向,这也是 Trusted Publishing 迁移正确的第一步——删除 token 被合理推迟到后续 PR,避免发布空窗。上一轮曾因本 PR 触及 release 流水线而将合入决定留给 maintainer——该确认现在由机制本身保证: 规模: 不适用——未触及核心包路径;4 个文件,+10/−7(三个 workflow YAML + 一个测试文件)。 方案:最小改动,范围正确。 每处都是同一个机械编辑——各 publish 点插入 风险: revert 历史高风险路径无命中(workflow YAML + 一个测试)。release 流水线的敏感性由上述 code owner 机制覆盖。 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewMy independent proposal for this goal was exactly what the PR does: insert
CI evidenceCI on the reviewed commit is fully green — both
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 On the behavioural claim — "published packages will carry provenance attestations" — no sandboxed lane settles it: neither 中文说明代码审查我为这个目标独立设想的方案与 PR 完全一致:在每处
CI 证据受审提交上的 CI 全绿——两个 关于行为性声明——"发布的包将携带 provenance 证明"——没有沙箱通道可以验证: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — the one blocker from the prior round landed exactly as prescribed, CI is green on the fix, and every claim in the PR verifies against the code. Stepping back: the previous run asked for exactly one thing — update the publish-command pin in On the release-pipeline sensitivity that made the prior run hold its vote: that sign-off is now structurally guaranteed rather than something this gate has to simulate — Six months from now this is the kind of change you're glad is there: provenance attestations on every published package for the cost of one flag per publish site and one test pin. Approving, pinned to the reviewed commit. Post-merge, run the release workflow with 中文说明回顾全局:上一轮只提出了一项修改——更新 关于上一轮因此扣住投票的 release 流水线敏感性:该确认现在由机制本身保证,而不需要门禁代为模拟—— 半年后回看,这是那种让人庆幸已合入的改动:每个发布的包都带上来源证明,代价只是每处 publish 加一个参数、一处测试固定。批准,投票锚定在受审提交。合入后请按 PR 自带测试计划先以 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Needs one fix before this can move forward — see my notes above. 🙏
@yiliang114 the release-workflow test pin in scripts/tests/package-scripts.test.js asserts the exact publish command, so the added --provenance flag turns those four assertions (and the ubuntu CI job) red. Update the pinned literal to npm publish --provenance "${PUBLISH_ARGS[@]}" and this should be green. Everything else checks out; once CI passes it still needs a maintainer's release-pipeline sign-off.
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
PR #9532 adds --provenance to every npm publish in the release pipeline. Update the workflow-pinning test to match the new command so the helper test suite stays green.
|
@qwen-code /triage The workflow-pinning test in |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 121 passed · 0 failed · 121 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:121 通过 · 0 失败 · 121 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #9532 Deep Verification —
|
| cell | workflows | test file | expected | observed |
|---|---|---|---|---|
| HH | head | head | green | green (16/16 tests) |
| BB | base (HEAD^1) |
base | green | green (control: suite was green before) |
| BH | base | head | red | red — AssertionError: expected … to contain 'npm publish --provenance "${PUBLISH_ARGS[@]}"', diff shows the base step text without the flag |
| HB | head | base | red | red — old assertion npm publish "${PUBLISH_ARGS[@]}" no longer matches |
The BH cell is the vacuity check: reverting the workflow hunks makes the new test fail on exactly the intended assertion (failure message quoted in logs/cell-BH.txt). The HB cell proves commit 1 alone would have broken CI, i.e. the test update in commit 2 is load-bearing in the other direction.
Table 2 — Publish replay at the npm seam (witness: 02-replay-publish-argv-base-vs-head.png)
Every publish run: block was extracted verbatim from the YAML of both arms (js-yaml on git show <commit>:<file>), ${{ }} expressions substituted with realistic values, and executed under GitHub's default shell contract (bash --noprofile --norc -eo pipefail) in a fake workspace with a recording npm stub (records argv; npm view → "not published"). 93/93 assertions:
| arm / mode | publish calls seen by stub | argvs with --provenance |
argvs with --dry-run |
normalized equality (head − flag == base) |
|---|---|---|---|---|
| head / real | 12 (3 single + 7 channel loop + sdk + mobile-mcp) | 12/12 | 0/12 | ✅ byte-identical |
| head / dry | 11 (mobile-mcp step skipped by if: !inputs.dry_run) |
11/11 | 11/11 | ✅ byte-identical |
| base / real | 12 | 0/12 (control) | 0/12 | — |
| base / dry | 11 | 0/11 (control) | 11/11 | — |
The normalized-equality rows are the "nothing else changed" proof: with --provenance stripped, head argv is byte-identical to base argv at every site in both modes. Replay is uncalibrated (no production-emitted artifact exists for publish steps in this token-less sandbox) — see Not covered.
Census (proving the negative)
A repo-wide census over *.yml/yaml/sh/js/mjs/ts/Makefile (excluding comments, tests, docs, dist/) finds exactly 6 real npm publish sites, all carrying --provenance, and zero pnpm/yarn publish sites: release.yml ×4 (audio-capture, qwen-code from dist/, channel-base, 7-package channel loop), release-sdk.yml ×1, cd-mobile-mcp.yml ×1. The only other artifact pipeline is build-and-publish-image.yml → ghcr.io (container image; npm provenance does not apply).
Permissions (the other half of the mechanism)
| workflow | job | effective id-token |
|---|---|---|
release.yml |
publish |
write (pre-existing) |
release-sdk.yml |
release-sdk |
write (pre-existing) |
cd-mobile-mcp.yml |
build-and-publish |
write (added by this PR) |
Verified by parsing each job's effective permissions (job-level, falling back to workflow-level) — scripted in 07-gate-assertions.sh.
Targeted gates (witness: 04-gates-census-suite-attribution.png)
All scripted, 14/14: census rows above; scripts test suite npm run test:scripts — 1324/1324 tests pass at head and at base with byte-identical attribution (one pre-existing environmental collection failure, see Not covered); repo actionlint clean on the three changed workflows at head (base equally clean) and proven live (planted malformed step → detected, exit 1); eslint clean on the changed test file and proven live (planted unused variable → @typescript-eslint/no-unused-vars, exit 1). yamllint could not be installed in this container (Not covered).
npm wire probe: what --provenance actually does (witness: 03-provenance-dry-run-skips-oidc.png)
Executed with the container's npm 10.9.8 (workflow's .nvmrc = 22 → npm 10.x; provenance needs ≥ 9.5) against a stub OIDC endpoint that logs every request, with a scratch package and a blackhole registry:
| probe | env | OIDC endpoint hits | result |
|---|---|---|---|
Q1 real publish --provenance |
full GH Actions env + auth | 1 (GET /oidc?audience=sigstore) |
consumes the token (IDENTITY_TOKEN_PARSE_ERROR on the stub's non-JWT) |
Q2 dry-run --provenance |
full GH Actions env | 0 | exit 0 |
Q3 real publish --provenance |
no CI env | 0 | fail-fast EUSAGE: Automatic provenance generation not supported for provider: null |
Q4 dry-run --provenance |
no CI env | 0 | exit 0 (flag merely parsed) |
| Q5 real publish, no flag | full GH Actions env | 0 | control: OIDC traffic comes from the flag |
Source corroboration (npm 10.9.8 shipped in this container): generateProvenance is called inside libpub() (libnpmpublish/lib/publish.js:140), and the CLI only calls libpub under if (!dryRun) (npm/lib/commands/publish.js:161) — dry-run structurally cannot generate provenance. Provider detection uses CI env vars set by the runner only when id-token: write is declared, which is why the mobile-mcp permission addition is load-bearing. Ruled out as non-issues: provenance source info comes from CI env (GITHUB_REPOSITORY etc., libnpmpublish/lib/provenance.js:37), not the package's repository field — so audio-capture/channel-* packages lacking that field are fine; and every site passes --access public, satisfying the new/private-package precondition (libnpmpublish/lib/publish.js:216).
Corrections
None.
Findings
Low — the PR's dry-run verification step cannot verify provenance (description overstates; no code change available or needed)
Step 3 of the Reviewer Test Plan ("trigger the release workflow with dry_run: true and confirm the publish steps complete") will pass — but it exercises nothing provenance-specific: probe Q2/Q4 show npm publish --provenance --dry-run exits 0 with zero OIDC requests, in or out of CI, and npm's source shows the attestation code only runs inside the !dryRun branch. The dry run proves the flag is accepted and the pipeline plumbing works; the attestation flow (OIDC fetch → sigstore signing → registry acceptance → Provenance badge) is first exercised by the first real release. The English body is careful about this; the Chinese summary's "完整行为验证通过 CI 的 dry-run 发布完成" overstates what the dry run verifies.
Reproduce: bash tmp/pr9532-verify-20260820-043832/06-probe-assertions.sh (Q2/Q4/Q5 rows).
Suggested handling: wording-only — e.g. "dry-run 验证 flag 兼容与流水线畅通;attestation 链路由合并后第一次真实发布验证". No code change is possible within this PR's scope (dry-run publishing nothing is by design), so this does not block.
Not covered
- yamllint could not run: the container ships no
pip(python3 -m pip→ "No module named pip";pip3 install --user→ Permission denied), andscripts/lint.js --setupreports "Failed to install yamllint". YAML validity of the three changed files is nonetheless established twice over: repo actionlint (repo ignore-list, exit 0 at head and base) parses them, and both harnesses here js-yaml-parse them successfully. - Replay is uncalibrated: no production-emitted artifact of a publish step is retrievable in a token-less sandbox. Calibration would require a real dry-run release log or an already-published package's manifest; base-arm validity rests on the suite's own expectations plus the BH/HB matrix cells.
- Per-commit attribution: the depth-2 checkout reaches only
HEAD^2; commit1ebf568c(workflows) sits past the shallow boundary (git rev-list HEAD^1..HEAD^2returns 1 of the 2 metadata commits,is-shallow-repositorytrue). The aggregateHEAD^1..HEADdiff — which contains both commits' changes — was verified throughout. - Full OIDC → sigstore → registry round trip: simulated only up to the token request (stub returned a non-JWT; npm correctly rejected it). Attestation acceptance by npmjs.com can only be observed with a real token and a real release.
- Repo-wide shellcheck/eslint/build gates: the PR touches no
.shfiles; the one changed.jsfile passed targeted eslint (gate proven live with a planted violation) and the full scripts suite. Repo-wide gates are already covered by the PR's own CI. install-script.test.jscollection failure in the scripts suite is pre-existing and environmental: the file throws at collection whenCI=trueandzipis missing (container hasunzipbut nozip). Base shows the byte-identical single failure (A/A), totals are 1324 passed on both arms, Δ +0/+0.- Container-image pipeline (
build-and-publish-image.yml→ ghcr.io): image provenance is a different mechanism, out of this PR's scope.
Methodology
Ran in the CI verify container (node:22-bookworm, node v22.23.2, npm 10.9.8) on the merge-ref checkout (HEAD = merge, HEAD^1 = base tip, HEAD^2 = PR head, depth 2). Base arm used git worktree add tmp/base-tree HEAD^1; the base side reused the root node_modules for dev tooling only (vitest, js-yaml) — the code under test is YAML text read by file, so no workspace symlink crosses into base-side runtime code. Harnesses live in the artifact dir: 01-ab-matrix.sh (4 vitest cells with expected-red encoding), 02-publish-replay.mjs (js-yaml extraction + expression substitution + bash --noprofile --norc -eo pipefail execution against a recording npm stub in a fake workspace), 03/04/05 probe scripts plus 06-probe-assertions.sh (stub OIDC HTTP server counting requests), 07-gate-assertions.sh (census + suite attribution + live-lint proofs). Raw per-cell stdout/stderr in logs/ (cell-HH/BB/BH/HB.txt, suite-head.txt, suite-base.txt, probe-assertions.txt, OIDC request logs). Two lint gates were proven live before being cited (planted broken YAML → actionlint exit 1; planted unused variable → eslint exit 1). During probing, one npm publish command accidentally ran at the repo root due to a shell cd/& precedence bug; it triggered only a lifecycle build (killed by timeout 60 before any publish attempt, registry was a blackhole, and git status confirmed zero tracked-file modifications).
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/package-scripts.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/package-scripts.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/package-scripts.test.js: PPPPP
verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · scripts/tests/package-scripts.test.js: P (exit 0)
round 2 · scripts/tests/package-scripts.test.js: P (exit 0)
round 3 · scripts/tests/package-scripts.test.js: P (exit 0)
round 4 · scripts/tests/package-scripts.test.js: P (exit 0)
round 5 · scripts/tests/package-scripts.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/cd-mobile-mcp.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release-sdk.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查——无阻断问题。 建议见行内评论。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/cd-mobile-mcp.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release-sdk.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.14)
| working-directory: 'packages/sdk-typescript' | ||
| run: |- | ||
| npm publish --access public --tag=${{ steps.version.outputs.NPM_TAG }} ${{ steps.vars.outputs.is_dry_run == 'true' && '--dry-run' || '' }} | ||
| npm publish --provenance --access public --tag=${{ steps.version.outputs.NPM_TAG }} ${{ steps.vars.outputs.is_dry_run == 'true' && '--dry-run' || '' }} |
There was a problem hiding this comment.
[Suggestion] The --provenance added here is pinned by no test. scripts/tests/package-scripts.test.js asserts npm publish --provenance "${PUBLISH_ARGS[@]}" for release.yml's four publish steps only, and release-sdk-workflow.test.js asserts just the persist-source logic — it never inspects this publish step. If a future edit (for example the second half of this Trusted Publishing migration, which rewrites this same line to drop NPM_TOKEN) removes --provenance, npm publish still succeeds, @qwen-code/sdk ships without a provenance attestation, and no test fails — silently undoing exactly the supply-chain guarantee this PR adds. Measured: reverting this file to the merge base leaves all 57 script-test files green. Suggested fix: in scripts/tests/release-sdk-workflow.test.js, extract the Publish @qwen-code/sdk step with the getWorkflowStep pattern from package-scripts.test.js and assert it contains npm publish --provenance.
中文说明
此处新增的 --provenance 没有任何测试固定。scripts/tests/package-scripts.test.js 仅对 release.yml 的四个 publish 步骤断言 npm publish --provenance "${PUBLISH_ARGS[@]}",而 release-sdk-workflow.test.js 只断言 persist-source 逻辑,从不检查这个 publish 步骤。若后续修改(例如本次 Trusted Publishing 迁移的第二阶段——重写同一行以移除 NPM_TOKEN)删掉了 --provenance,npm publish 仍会成功,@qwen-code/sdk 将在没有来源证明的情况下发布,且没有任何测试失败——悄悄瓦解本 PR 正要建立的供应链保障。实测:将该文件还原到合并基点后,全部 57 个脚本测试文件依然通过。建议修复:在 scripts/tests/release-sdk-workflow.test.js 中用 package-scripts.test.js 的 getWorkflowStep 写法提取 Publish @qwen-code/sdk 步骤,断言其包含 npm publish --provenance。
— qwen3.8-max via Qwen Code /review (v0.21.14)
| if: '${{ !inputs.dry_run }}' | ||
| working-directory: 'packages/mobile-mcp' | ||
| run: 'npm publish --access public' | ||
| run: 'npm publish --provenance --access public' |
There was a problem hiding this comment.
[Suggestion] Neither of the two lines this diff adds to this workflow is pinned by any test — no test references cd-mobile-mcp.yml at all. Two regressions would ship green: dropping --provenance here (the mobile-mcp package would silently publish without an attestation), and dropping the id-token: 'write' permission this PR adds — which looks unused, because nothing else in this job consumes an OIDC token — so the release dies at publish time on its next real run with npm's Provenance generation in GitHub Actions requires "write" access to the "id-token" permission. Measured: reverting this whole file to the merge base leaves all 57 script-test files green. This job was the only one that needed a permission addition in this PR, which makes it the most fragile point of the migration and the least guarded. Suggested fix: add a small workflow test (same pattern as release-sdk-workflow.test.js) asserting the publish job declares id-token: 'write' and the Publish step contains npm publish --provenance --access public.
中文说明
本 diff 给该 workflow 新增的两行都没有任何测试固定——没有任何测试引用 cd-mobile-mcp.yml。两类回归都可以在 CI 全绿的情况下合入:删掉此处的 --provenance(mobile-mcp 包将在没有来源证明的情况下静默发布);或删掉本 PR 新增的 id-token: 'write' 权限——由于该 job 中没有其他步骤使用 OIDC token,这行看起来像是无用的——于是发布在下次真实运行时因 npm 报错 Provenance generation in GitHub Actions requires "write" access to the "id-token" permission 而失败。实测:将该文件整体还原到合并基点后,全部 57 个脚本测试文件依然为绿。该 job 是本 PR 中唯一需要补充权限的发布点,是迁移中最脆弱、却也最缺乏防护的一环。建议修复:新增一个小型 workflow 测试(与 release-sdk-workflow.test.js 相同的写法),断言 publish job 声明了 id-token: 'write',且 Publish 步骤包含 npm publish --provenance --access public。
— qwen3.8-max via Qwen Code /review (v0.21.14)
| exit 0 | ||
| fi | ||
| npm publish "${PUBLISH_ARGS[@]}" | ||
| npm publish --provenance "${PUBLISH_ARGS[@]}" |
There was a problem hiding this comment.
[Suggestion] The ~10-line "already-published guard + npm publish" block is repeated verbatim four times in this file (audio-capture, qwen-code, channel-base, and this channel loop), with two differently-shaped siblings in release-sdk.yml and cd-mobile-mcp.yml. This one-flag change required 7 coordinated edits (6 workflow sites + the test mirror), and the copies already drifted once: id-token: write was present on the release.yml and release-sdk.yml jobs but had to be added by hand to cd-mobile-mcp.yml in this very PR. The second half of the Trusted Publishing migration (replacing NODE_AUTH_TOKEN/NPM_TOKEN auth with OIDC) must land consistently on all six copies again — missing one still publishes successfully, leaving exactly one package without attestation (or still on the legacy token) with no CI signal except the release.yml-only mirror test. Suggested fix: extract the guard + publish into one shared helper — e.g. .github/scripts/publish-npm-package.sh (.github/scripts/ already hosts workflow helpers) or a composite action under .github/actions/ — so publish-policy flags and auth wiring live in one place.
中文说明
这段约 10 行的「已发布检查 + npm publish」代码块在本文件中逐字重复了 4 次(audio-capture、qwen-code、channel-base 以及此处的 channel 循环),在 release-sdk.yml 与 cd-mobile-mcp.yml 中还有两处形态不同的同类代码。仅加一个参数就需要 7 处协同修改(6 处 workflow + 测试镜像),而且漂移已经发生过一次:id-token: write 在 release.yml 和 release-sdk.yml 的 job 上原本就有,却需要在本 PR 中手动补给 cd-mobile-mcp.yml。Trusted Publishing 迁移的第二阶段(用 OIDC 替换 NODE_AUTH_TOKEN/NPM_TOKEN 认证)同样需要在全部六处一致落地——漏掉任何一处,发布依然会成功,恰好有一个包不带来源证明(或仍在使用长期 token),而除了仅覆盖 release.yml 的镜像测试外没有任何 CI 信号。建议修复:把「已发布检查 + 发布」抽成一个共享辅助——例如 .github/scripts/publish-npm-package.sh(.github/scripts/ 已存放 workflow 辅助脚本)或 .github/actions/ 下的 composite action——让发布策略参数与认证接线只维护在一处。
— qwen3.8-max via Qwen Code /review (v0.21.14)
|
Released in v0.21.15. |




What this PR does
Every
npm publishin the release pipelines now passes--provenance, so each published package carries an npm-signed attestation of where and how it was built. The mobile-mcp publish job also gains theid-token: writepermission that provenance attestation requires; the main release and SDK release jobs already declare it. Authentication still uses the existing npm token — nothing about how publishing authenticates changes in this PR.Why it's needed
The internal npm supply-chain audit flags our releases as unverifiable: without provenance, consumers cannot confirm a package really came from this repository's CI, and a compromised registry account or stolen token could ship a tampered build that looks identical. Provenance closes that gap and is also the required first half of the Trusted Publishing migration (the token removal lands in a follow-up PR, after Trusted Publishers are bound on npmjs.com, so there is no window where publishing could break).
Reviewer Test Plan
How to verify
npm publishcall sites (four in the main release workflow, one in the SDK release workflow, one in the mobile-mcp workflow) now include--provenanceand that no other publish site exists.id-token: write(main release and SDK release already did; mobile-mcp gains it here).dry_run: trueand confirm the publish steps complete; the first real release afterwards should show a Provenance badge on the published packages on npmjs.com.Evidence (Before & After)
N/A (CI workflow change, no user-visible behavior)
Tested on
Environment (optional)
YAML validity verified locally for all three changed workflows; full behavior verification happens in CI via a dry-run release.
Risk & Scope
--provenancerequires the job to holdid-token: write; all three publishing jobs now declare it, so this is satisfied. Publishing still authenticates with the existing token, so this PR cannot break a release on its own.Linked Issues
Part of the internal npm package supply-chain security audit remediation.
中文说明
这个 PR 做了什么
发布流水线中的每一处
npm publish现在都带上了--provenance参数,使每个发布的包都附带一份由 npm 签名的来源证明(attestation),记录它是在哪里、如何构建的。mobile-mcp 的发布 job 同时补上了 provenance 签名所需的id-token: write权限;主发布和 SDK 发布的 job 此前已声明该权限。认证方式仍然使用现有的 npm token——本 PR 不改变发布的任何认证行为。为什么需要
内部 npm 供应链审计指出我们的发布不可验证:没有 provenance,使用方无法确认一个包确实来自本仓库的 CI,一旦 registry 账号被入侵或 token 泄漏,攻击者可以发布外观完全一致的篡改版本。Provenance 堵上了这个缺口,同时也是 Trusted Publishing 迁移必需的前半段(删除 token 会在后续 PR 中进行,前提是 npmjs.com 上先绑定好 Trusted Publisher,确保不存在发布中断的窗口期)。
评审测试计划
如何验证
npm publish(主发布 workflow 4 处、SDK 发布 workflow 1 处、mobile-mcp workflow 1 处)都已加上--provenance,且不存在其他发布点。id-token: write(主发布和 SDK 发布原本就有;mobile-mcp 在本 PR 中补上)。dry_run: true触发一次发布 workflow,确认发布步骤正常完成;之后第一次真实发布后,npmjs.com 上的包应出现 Provenance 徽章。前后对比证据
N/A(CI workflow 变更,无用户可见行为)
测试环境
三个改动 workflow 均已在本地验证 YAML 合法性;完整行为验证通过 CI 的 dry-run 发布完成。
风险与范围
--provenance要求 job 持有id-token: write,三个发布 job 现在都已声明,条件满足。发布仍用现有 token 认证,因此本 PR 单独不会导致发布失败。关联 Issue
内部 npm 包供应链安全审计整改的一部分。