Skip to content

We use analytics cookies to see how the site is used. They are only set if you accept. Accepting also turns on session recording (pages viewed, clicks and scrolls) so we can fix problems; what you type stays hidden. Read the privacy policy

CodeTrendy
A release pipeline where a crossed-out NPM_TOKEN key is replaced by a short-lived OIDC token checked at the registry

Developer Tools / 9 min read / no secrets were stored in this release

npm Trusted Publishing: Ship Releases From CI Without an NPM_TOKEN Before January 2027

npm targets January 2027 to stop access tokens from publishing new versions directly. Trusted publishing replaces the NPM_TOKEN secret with a short-lived OIDC identity from GitHub Actions, GitLab or CircleCI. How it works, how to set it up, and what to do when CI cannot use it.

If the NPM_TOKEN secret in your release workflow leaks, whoever holds it can publish a new version of your package from their own machine until it expires or you revoke it. Trusted publishing removes that secret: your CI job proves which repository and workflow it is, npm checks that identity against the publisher you registered for the package, and only then accepts the publish.

Short answer

  • On GitHub Actions, GitLab.com or CircleCI cloud: register the workflow as a trusted publisher on npmjs.com, add id-token: write (or the GitLab and CircleCI equivalent) and remove the token from the publish step. Confirm one release goes out, then delete NPM_TOKEN and set the package to disallow tokens.
  • On a self-hosted runner or another CI: trusted publishing does not work there yet. Switch to a stage-only token and approve each release with 2FA.
  • Why now: npm is targeting January 2027 to remove direct publishing with access tokens.

What is npm trusted publishing?

Trusted publishing lets a CI/CD workflow publish an npm package with OpenID Connect (OIDC) instead of a stored npm token. npm's docs describe it as "eliminating the need for long-lived npm tokens": each publish uses "short-lived, cryptographically-signed tokens that are specific to your workflow and cannot be extracted or reused."

It is not an npm invention. A paper from an Open Source Security Foundation (OpenSSF) working group describes Trusted Publishers as an authentication method any public package registry can adopt: CI exchanges an OIDC identity token for a short-lived, tightly scoped API token. PyPI shipped it first, in April 2023; RubyGems followed in December 2023, then crates.io and npm in July 2025 and NuGet in September 2025. npm's version became generally available on 31 July 2025.

Why move off NPM_TOKEN now?

Because the token path is closing, step by step. On 14 September 2025 GitHub was told about Shai-Hulud, a self-replicating worm that spread through compromised maintainer accounts and stole secrets, npm tokens among them. GitHub removed more than 500 compromised packages and announced that publishing would narrow to three options: local publishing with 2FA, granular tokens limited to seven days, and trusted publishing.

  1. Jul 2025

    Trusted publishing generally available for GitHub Actions and GitLab.com runners.

  2. Sep 2025

    Shai-Hulud. GitHub announces the move away from long-lived tokens.

  3. Nov 2025

    Legacy (classic) access tokens removed. Only granular tokens remain.

  4. Aug 2026

    Bypass-2FA tokens can no longer change email, password, 2FA, tokens or maintainers.

  5. Sep 2026

    Stage-only tokens: CI can submit a version, a maintainer approves it with 2FA.

  6. Oct 2026

    npm stage publish can create a brand-new package.

  7. Jan 2027

    Target date to remove direct publishing with access tokens.

January 2027 is a target, not a promise, and the two sources word its scope differently. The changelog says npm "is targeting" the removal of direct publishing through bypass-2FA tokens. The access token docs say "The ability to publish new package versions directly with a granular access token will be removed in January 2027". Either way, a release job that runs npm publish with a token is on borrowed time.

Token, trusted publisher or stage-only token: which should publish?

Three ways a CI job can still get a version onto the registry, compared on what matters when a secret leaks.

How each CI publishing method behaves, per npm's docs (October 2026)
Granular token, publishTrusted publisherStage-only token
Secret stored in CIYesNoneYes
Works fromAny machineOnly the registered workflow, on a cloud runnerAny machine, but only stages
LifetimeExpiry you set, at least 1 dayShort-lived, per runExpiry you set, at least 1 day
ProvenanceOnly with --provenance, on GitHub or GitLab cloud runnersAutomatic (public repo and package, not CircleCI)Not covered by the stage-only docs
Human approvalNoOptional: allow stage publish onlyAlways, with 2FA
After January 2027Direct publish with a token removed (target)Not part of the announced removal; npm's recommended pathnpm's migration path for token-based CI

The pick is short: use a trusted publisher wherever your CI supports one, and a stage-only token everywhere else.

How does trusted publishing check a workflow?

Nothing secret is stored. Each run asks its CI provider for a fresh identity and trades it for a publish credential that dies with the run.

  1. CI run

    Your workflow starts on a cloud-hosted runner, with permission to request an OIDC ID token (id-token: write on GitHub).

  2. CI provider

    Issues a signed ID token that names the repository, the workflow file and, if set, the environment.

  3. npm CLI

    Detects the OIDC environment on its own and sends that token to the registry before it falls back to any npm token.

  4. npm registry

    Compares the claims with the trusted publisher you saved for the package. Every field must match exactly, case included.

  5. Short-lived token

    On a match, npm hands back a publish token valid for that workflow only. It cannot be pulled out and reused.

  6. Provenance

    From GitHub Actions or GitLab, with a public repository and a public package, the publish carries a provenance attestation, signed through Sigstore and logged in a public transparency log.

The trusted publishing exchange, step by step, as npm documents it. The burning bar stands for the short-lived publish token: there is no lasting credential to leak.

How do you set up trusted publishing on GitHub Actions?

  1. Check the versions

    The job needs npm CLI 11.5.1 or later and Node 22.14.0 or later. Print npm --version in the job; if it is older, update npm before the publish step.

  2. Match repository.url

    The repository.url in package.json must match your GitHub repository exactly. Forks that never changed it fail here.

  3. Register the publisher

    On npmjs.com open the package, then Settings, then Trusted publishing, and pick GitHub Actions. Fill in the owner, the repository, the workflow file name only (publish.yml, not the path) and the environment if you use one. Choose the allowed actions: npm stage publish is always allowed, npm publish is your choice.

  4. Edit the workflow

    Grant id-token: write, run on a GitHub-hosted runner, and remove NODE_AUTH_TOKEN from the publish step.

  5. Publish within 2 days

    A new trusted publisher must complete its first successful publish within 2 days, or it expires and has to be deleted and created again.

  6. Lock the door behind you

    In the package's Settings, Publishing access, choose "Require two-factor authentication and disallow tokens", then revoke the automation tokens you no longer need.

What does the release workflow look like?

A tag-triggered release, adapted from the example in npm's docs. The only lines that make it trusted are the id-token permission and the absence of any token on the publish step.

.github/workflows/publish.yml
name: Publish
on:
  push:
    tags: ['v*']

permissions:
  id-token: write   # lets the job request an OIDC token
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest   # GitHub-hosted, not self-hosted
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
          package-manager-cache: false
      - run: npm ci
      - run: npm test
      - run: npm publish   # no NODE_AUTH_TOKEN here

Prefer the command line? With npm 11.15.0 or later, 2FA on your account, write access to a package that already exists, and an interactive login (not a bypass-2FA token), one command does step 3:

Terminal
npm trust github my-package --file publish.yml --repo my-org/my-package --allow-publish

Why does the publish fail with ENEEDAUTH?

"Unable to authenticate" (ENEEDAUTH) usually means the job's identity or setup does not match what npm expects. Check these in order.

Workflow file name differs
Fields are case-sensitive and the name needs its .yml or .yaml extension.
Self-hosted runner
Not supported. Use a GitHub-hosted runner, GitLab.com shared runners or CircleCI cloud.
No id-token: write
Without it the job cannot request an OIDC token at all.
Reusable or manual workflow
With workflow_call or workflow_dispatch, npm checks the calling workflow's name, and both workflows need id-token: write.
repository.url mismatch
It must match the GitHub repository exactly.
npm ci fails, not the publish
Trusted publishing covers the publish only. Private dependencies still need a read-only granular token for the install step.

Which path fits your CI?

Does your release job run on GitHub-hosted runners, GitLab.com shared runners or CircleCI cloud?

Yes: use a trusted publisher. Then one more question:

Should a person approve every release before it goes live?

Yes: allow only npm stage publish on the trusted publisher. A maintainer approves each staged version with 2FA, on the CLI or npmjs.com.

No: also allow npm publish, then disallow tokens on the package.

No (self-hosted runner or another CI): create a granular token with "Read and write (stage only)", run npm stage publish, and approve with npm stage approve <stage-id>.

Before you delete the old token

  • The trusted publisher has completed one real publish (within its 2-day window).
  • If a person should approve each release, the trusted publisher allows npm stage publish only.
  • Every package you publish has its own trusted publisher: the setting is per package.
  • Install steps that need private packages use a read-only token, never the old write token.
  • Publishing access is set to "Require two-factor authentication and disallow tokens".
  • Old automation tokens are revoked on npmjs.com and removed from CI secrets.

Questions maintainers ask

Does trusted publishing work for private and scoped packages?

Yes. At launch npm said it covers all private and public packages, scoped and unscoped. Provenance is the exception: only a public package published from a public repository gets it.

Do I still need the --provenance flag?

No. A trusted publish from GitHub Actions or GitLab adds provenance by default when the repository and package are public. CircleCI publishes carry no provenance yet. You can opt out with NPM_CONFIG_PROVENANCE=false.

Can one package have more than one trusted publisher?

On npmjs.com, yes: up to 10 at the same time, for example GitHub Actions and GitLab side by side. The npm trust command docs still say one configuration per package, so add extra ones on the website. A saved connection cannot be edited: delete it and create a new one.

How do I publish a package that does not exist yet?

npm trust needs an existing package. Since October 2026, npm stage publish can create one, from a local session or a granular token. A public placeholder version, 0.0.0-stage, appears at once; your real first version waits for a maintainer to approve it. Then add the trusted publisher.

Can I use a self-hosted runner?

Not yet. npm says self-hosted runner support is planned for a future release. Until then, use a stage-only token from that runner.