Deep dive · Software supply chain

npm Trusted Publishing, OIDC, and what changed

One npm package can now trust several specific release workflows. Here is how OIDC removes long-lived publishing secrets—and why staging, scanning and human approval make the change more important than it first appears.

September 4, 202610 minute readPractical explainer

The announcement is easy to miss because it assumes you already understand npm Trusted Publishing and OIDC. The core change is simpler: npm packages are no longer limited to one trusted publishing configuration.

First: what is Trusted Publishing?

A conventional GitHub Actions workflow often stores an npm token as a repository secret:

env:
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

That token is a long-lived credential. If it is exposed, an attacker may be able to publish a malicious version until the token is detected and revoked.

Trusted Publishing replaces that permanent write credential with a short-lived, cryptographically signed OIDC identity issued to the running workflow.

GitHub Actions
      │
      │ temporary OIDC identity
      ▼
     npm
      │
      │ trusts this repository and workflow
      ▼
publish or stage allowed
npm verifies the workflow identity before allowing publication

The workflow requests permission to obtain an identity token:

permissions:
  id-token: write
  contents: read

steps:
  - run: npm publish

The npm CLI detects the OIDC environment and exchanges that identity for short-lived publishing authorization. No NPM_TOKEN is required for the publish operation.

Compatibility note

npm documents Trusted Publishing for cloud-hosted GitHub Actions, GitLab.com shared runners and CircleCI cloud. It currently requires npm CLI 11.5.1 or later and Node.js 22.14.0 or later; self-hosted runners are not yet supported.

One package can now trust several release paths

Imagine @stackmoments/ui has separate workflows for stable and prerelease versions:

.github/workflows/publish.yml
        └── @stackmoments/ui 1.5.0

.github/workflows/prerelease.yml
        └── @stackmoments/ui 1.6.0-beta.1
Two release workflows targeting the same npm package

Previously, one trusted-publisher configuration per package made this awkward. Additional release paths could require workflow workarounds or a long-lived npm token.

Now the same package can define multiple independently authorized configurations:

Trusted Publisher 1
Repository: stackmoments/ui
Workflow: publish.yml
Environment: production

Trusted Publisher 2
Repository: stackmoments/ui
Workflow: prerelease.yml
Environment: beta

Trusted Publisher 3
Repository: stackmoments/release-tools
Workflow: release.yml
Environment: production

npm authorizes the operation when the incoming OIDC identity matches any one configuration. The configurations behave like an allow-list:

               ┌── publish.yml ─────── ✓
npm package ────┼── prerelease.yml ──── ✓
               └── release.yml ─────── ✓
Each approved workflow is an independent path to the package
The subtle security point

The rules are additive: configuration A or configuration B can authorize a release. They do not restrict one another, and their evaluation order is not guaranteed. Do not design policy that depends on one rule running before another.

Where multiple configurations help

PurposeWorkflowResult
Stable releaserelease.yml2.3.0
Beta releasebeta.yml2.4.0-beta.1
Nightly buildnightly.yml2.4.0-nightly…
Manual production releaseproduction.ymlStable package

Each path can use its own repository, workflow filename and optional environment. npm currently allows up to ten trusted publishers per package.

The announcement contains three meaningful updates

01

Multiple configurations

A package can authorize several CI/CD providers or workflows. Each connection is managed independently, and a matching connection can publish or stage the package.

02

Staging is safer

Every new trusted configuration can stage a package by default. Direct npm publish access is an explicit option for that configuration.

03

Approval waits for scanning

A staged package cannot be approved while its malware scan is running. After scanning, a maintainer can approve or reject it, and the Versions tab records that history.

Staging changes the failure path of a compromised workflow:

GitHub Actions
      │ OIDC
      ▼
npm stage publish
      ▼
malware scan
      ▼
maintainer approval
      ▼
public npm registry
Staging adds automated and human gates before public release

With direct publishing, compromised release automation can push straight to the registry. With stage-only authorization, the workflow can prepare a version, but it cannot make that version public without the later scan and approval gate.

Publishing authentication is not installation authentication

Trusted Publishing authenticates npm publish and npm stage publish. It does not automatically give npm ci access to private dependencies.

# Private dependency installation
- run: npm ci
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_READ_TOKEN }}

# Package publication
- run: npm publish
  # OIDC — no publishing token

If a release build installs private packages, npm recommends a granular read-only token for that step and OIDC for publication. This is still substantially safer than placing a permanent write token in CI.

What should you remember?

One npm package can now trust several specific CI release workflows through OIDC, while stage-only permissions, malware scanning and human approval make the path to the public registry safer.