Skip to content

security(release): drop .npmrc NPM_TOKEN write, rely on npm trusted publishing#14011

Merged
chenjiahan merged 1 commit into
mainfrom
security/npm-oidc-publish
May 12, 2026
Merged

security(release): drop .npmrc NPM_TOKEN write, rely on npm trusted publishing#14011
chenjiahan merged 1 commit into
mainfrom
security/npm-oidc-publish

Conversation

@stormslowly

Copy link
Copy Markdown
Contributor

Summary

  • Delete the ~/.npmrc _authToken write from scripts/release/publish.mjs.
  • pnpm publish now authenticates against npmjs.com via the workflow's OIDC token (npm trusted publishing), instead of a long-lived NPM_TOKEN secret.
  • Removes the highest-blast-radius credential in the release pipeline — the same class of static token whose exfiltration enabled the TanStack 2026-05-11 npm supply-chain compromise.

Why this is needed even though --provenance is already set

--provenance and trusted publishing are independent:

  • --provenance: signs a SLSA attestation about the build using the workflow's OIDC token. Already enabled.
  • Trusted publishing: replaces the publish credential itself with an OIDC token. Requires (a) npmjs.com trusted publisher binding per package, and (b) no static _authToken to shadow it.

On pnpm < 11.0.7 (rspack is on pnpm@10.33.4), a configured _authToken takes precedence over OIDC. Therefore, despite trusted publishers being configured on npmjs.com, releases have been authenticating with the long-lived NPM_TOKEN all along. Removing the .npmrc write is what actually flips the release flow to OIDC.

Prerequisite (must already be true before merge)

  • Every @rspack/* package on npmjs.com has a Trusted Publisher binding for:

    • web-infra-dev/rspack + workflow reusable-release-npm.yml + environment npm
    • web-infra-dev/rspack + workflow release-canary.yml + environment npm-canary
    • web-infra-dev/rspack + workflow release-debug.yml + environment npm-canary

    Without these bindings on every workspace package (12+: @rspack/core, @rspack/cli, @rspack/binding plus the 10 @rspack/binding-<platform> packages, etc.), pnpm 10.x has no fallback to static token and the corresponding package's pnpm publish will 401.

Diff

- const npmrcPath = `${process.env.HOME}/.npmrc`;
  ...
- if (fs.existsSync(npmrcPath)) {
-   console.info('Found existing .npmrc file');
- } else {
-   console.info('No .npmrc file found, creating one');
-   fs.writeFileSync(
-     npmrcPath,
-     `//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}`,
-   );
- }

12 deletions, 0 additions.

Test plan

  • Trigger Release Canary with dry_run: true (manually via workflow_dispatch). Confirm pnpm publish in the log negotiates auth via OIDC — log line should reference the trusted publisher / OIDC handshake rather than reading _authToken from ~/.npmrc. If any package 401s, that package's trusted-publisher binding on npmjs.com is missing or misconfigured — fix before merge.
  • After dry-run passes, trigger a non-dry canary release on latest tag to confirm a real publish flows through.
  • Confirm the published canary tarball still ships with provenance attestation (visible via npm view @rspack/binding-wasm32-wasi --jsondist.attestations).

Required after-merge actions

  • Delete NPM_TOKEN secret from the npm GitHub environment.
  • Delete NPM_TOKEN secret from the npm-canary GitHub environment.

After both deletions, the long-lived credential has zero remaining consumers in the repo (grep -rn NPM_TOKEN . should match only documentation or this PR's history).

Follow-ups (separate PRs)

  • Bump pnpm to >= 11.0.7 so OIDC takes precedence over any future _authToken regression, and so failed OIDC negotiation transparently falls back to a static token (defense in depth).
  • Add a regression guard in publish.mjs that hard-fails if process.env.NPM_TOKEN is set, preventing accidental reintroduction.

…ublishing

The previous publish flow wrote a static `_authToken` from the
`NPM_TOKEN` secret into `~/.npmrc` before invoking `pnpm publish`. On
pnpm < 11.0.7 a configured `_authToken` takes precedence over OIDC
trusted publishing, so even though all `@rspack/*` packages have a
trusted publisher binding configured on npmjs.com, releases were still
authenticating with a long-lived token. The static-credential surface
is exactly what enables the TanStack 2026-05-11-style attack: anything
that runs in the `npm`/`npm-canary` environment can exfiltrate
`process.env.NPM_TOKEN` and publish arbitrary versions.

Deleting the `.npmrc` write makes OIDC the only auth path. `pnpm
publish` now mints a per-run token via the workflow's `id-token: write`
permission and authenticates against the trusted publisher binding
configured on npmjs.com.

Provenance (`--provenance` flag) is unaffected; it was already using
OIDC for signing and is independent of publish auth.

After merge, the `NPM_TOKEN` secret in the `npm` and `npm-canary`
GitHub environments has no remaining consumer and should be removed
from environment secrets to fully retire the credential.
Copilot AI review requested due to automatic review settings May 12, 2026 07:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the npm release pipeline by removing the step that writes a long-lived npm auth token into ~/.npmrc, so publishing can rely on npm Trusted Publishing (OIDC) instead of a static NPM_TOKEN.

Changes:

  • Remove ~/.npmrc _authToken creation logic from the release publish script.
  • Ensure pnpm publish uses the workflow-provided OIDC flow (Trusted Publishing) rather than a repository/environment secret.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

await $`pnpm publish -r ${options.dryRun ? '--dry-run' : ''} --tag ${
options.tag
} --no-git-checks --provenance`;

P1 Badge Bind trusted publishing to the caller workflow

In the stable release path I checked, .github/workflows/release.yml calls reusable-release-npm.yml, and npm’s trusted-publishing troubleshooting docs state that for workflow_call / workflow_dispatch publishes, validation checks the calling workflow rather than the workflow containing npm publish (https://docs.npmjs.com/trusted-publishers/). After this change removes the static .npmrc token fallback, pnpm publish here will 401 for stable releases if the npm packages are bound to reusable-release-npm.yml instead of the caller release.yml; please update the trusted-publisher bindings or keep a fallback until that migration is complete.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

Copy link
Copy Markdown
Contributor

📦 Binary Size-limit

Comparing 4e6e2bc to refactor(core): remove unused exports final name metadata (#14003) by harpsealjs

🙈 Size remains the same at 61.95MB

@github-actions

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

Found 6 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Change
popular-libs 1.7 MB 0
react-10k 5.7 MB 0
react-1k 826.3 KB 0
react-5k 2.7 MB 0
rome 1.6 MB 0
ui-components 4.8 MB 0

Generated by Rsdoctor GitHub Action

@codspeed-hq

codspeed-hq Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 34 untouched benchmarks
⏩ 25 skipped benchmarks1


Comparing security/npm-oidc-publish (4e6e2bc) with main (259b48a)

Open in CodSpeed

Footnotes

  1. 25 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@chenjiahan chenjiahan merged commit 07ab44c into main May 12, 2026
56 checks passed
@chenjiahan chenjiahan deleted the security/npm-oidc-publish branch May 12, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants