Skip to main content

Your npm Token Is the Next Supply-Chain Incident: A Tokenless Publishing Playbook for Git-Push Pipelines

10 min readDora NodaDora Noda
Share
On this page

In May 2026, attackers pushed 84 malicious versions across 42 @tanstack packages in a single day. Two weeks earlier, a different wave had compromised 172 npm and PyPI packages in 48 hours by stealing CI credentials and reusing them to publish. In August, the keyv wave hit more than 400 packages with over two billion monthly installs within hours. Every one of these 2026 supply-chain waves turned on the same lever: a valid credential that could publish, sitting somewhere an attacker could reach.

If your release pipeline still publishes with a long-lived NPM_TOKEN in repo secrets, that lever is yours. The fix is npm's OIDC trusted publishing — generally available since July 2025, extended to CircleCI in April 2026 — and the migration is about ten lines of workflow YAML. Here is the whole change, before and after:

yaml
# BEFORE: a secret that lives forever and publishes from anywhere
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
      - run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
yaml
# AFTER: no secret at all — the workflow proves who it is, per run
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write # the entire migration, plus deleting the secret
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
      - run: npm publish --access public

Delete NPM_TOKEN from repo secrets, register the repo as a trusted publisher on npmjs.com, and there is nothing left to steal: no token to exfiltrate, no 90-day rotation to forget, and every publish carries a provenance attestation linking the tarball back to the exact repo, commit, and workflow run.

The rest of this post is the complete playbook — how the exchange works, the six migration steps with their failure modes, what npm tightened in 2026 and what disappears in January 2027, the honest limits of OIDC, and what the same pattern means for a self-hosted PaaS build pipeline.

How trusted publishing actually works

OIDC trusted publishing replaces "here is a secret that proves I may publish" with "here is a signed identity document proving which workflow, in which repo, is running right now." Four steps, all handled by the npm CLI:

  1. The CI runner mints a short-lived OIDC JWT identifying the repo, the workflow file, and the run. On GitHub Actions this needs only the id-token: write permission; nothing is stored.
  2. The npm CLI (version 11.5.1 or newer) hands that JWT to the registry instead of a password or token.
  3. The registry checks the JWT against the trusted-publisher config pinned to the package: same org or user, same repo, same workflow filename, same environment. Anything else is rejected.
  4. The registry returns a publish credential valid for that one operation, and the publish lands with a provenance attestation generated automatically. The explicit --provenance flag is redundant once trusted publishing is in play.

Three facts constrain the setup. First, minimum versions: npm 11.5.1 with Node 22.14 or newer for GitHub Actions and GitLab CI, and npm 11.11.0 or newer for CircleCI — the documented 11.5.1 floor does not hold there, a gotcha that has broken real migrations.

Second, supported providers are GitHub Actions, GitLab CI, and CircleCI, the last one added on April 6, 2026; anything else (Forgejo, Buildkite, a hand-rolled runner) still publishes with tokens. Third, the package must already exist on the registry before a trusted publisher can be attached, so every migration starts with one last old-style publish.

The six-step migration checklist

Each step names the exact action and the failure mode teams actually hit.

1. Bootstrap the package if it is new. Run one manual npm publish with interactive login and one-time-password 2FA. Trusted-publisher config attaches only to an existing package, so greenfield packages cannot skip this. Verify with npm view <name> versions before continuing.

2. Register the trusted publisher on npmjs.com. Open the package page, go to Settings, then Trusted Publisher, and fill in the provider, org or user, repo, workflow filename, and environment exactly — casing included. The single most common failure is a near-miss here: a renamed workflow file or a wrong-case org silently fails validation at publish time, months after setup, when nobody remembers the registration screen.

3. Rewire the workflow and delete the secret. Add id-token: write to the publish job's permissions, remove the NODE_AUTH_TOKEN wiring from the publish step, and delete NPM_TOKEN from repo secrets. If you use reusable workflows (release-please and friends), register the caller workflow's filename, not the reusable file's — npm authorizes the workflow that requested the OIDC token, and authorizing the wrong filename is the second most common silent failure.

4. Confirm provenance, then drop the flag. Check that published versions show the provenance badge on the npm page, which links the tarball to the repo, commit, and run. Once confirmed, --provenance can come out of the publish command: trusted publishing attests automatically, and keeping the flag only preserves confusion about which mechanism is doing the work.

5. Set the action allowlist. Trusted-publisher configs created after May 20, 2026 must explicitly select which npm actions the publisher may perform — npm publish, at minimum — instead of defaulting open. Older configs carried over with broader grants, so revisit them: an allowlist left blank-by-default is the 2026 equivalent of the wildcard token scope everyone just deleted.

6. Revoke the old tokens and lock the account. Revoke every automation token the migration replaced, then enable the "require 2FA and disallow tokens" setting on the package so a resurrected token cannot publish. npm's own docs walk this revocation explicitly because step 3 without step 6 is theater: the OIDC path is clean while the old token still works from anywhere.

pnpm users get the same flow for free — pnpm publish performs the OIDC exchange through npm-registry-fetch — and the ecosystem is already moving: Snyk spent mid-2026 migrating its plugin fleet off a shared admin account's read-write tokens onto CircleCI OIDC before retiring that account on August 1, 2026.

What 2026 changed, in one timeline

The migration above lands differently depending on when you last looked at npm security. The short version:

WhenWhat changedWhy it matters to you
July 2025Trusted publishing goes GATokenless publish becomes the supported default, not an experiment
December 2025npm revokes classic tokensThe oldest token format dies; granular tokens become the floor
April 2026CircleCI becomes a trusted publisherThe second major CI provider joins; GitHub Actions is no longer the only path
May 2026Staged publishing ships; new publishers must allowlist actionsCI can publish to a holding queue, but only a human passing a live 2FA challenge releases it publicly — stolen automation tokens can no longer silently ship
July 2026npm 12 disables install scripts by default; bypass-2FA tokens lose account and org actionsThe blast radius of a stolen token shrinks on both the install side and the management side
January 2027Bypass-2FA tokens lose direct publishing entirelyAny token that skips 2FA becomes stage-only: it can queue a release, never ship one

Two rows deserve emphasis. Staged publishing is the human gate that OIDC alone cannot provide: configured stage-only, npm publish from a workflow is rejected outright while npm stage publish is accepted, and approval requires interactive authentication that no OIDC token or granular token can satisfy.

And the January 2027 deadline is the forcing function — teams still on bypass-2FA tokens after that date keep a publish path that can only queue, which is another way of saying the migration stops being optional.

What OIDC does not fix

A playbook that pretends OIDC ends supply-chain attacks would be dishonest, because May 2026 proved otherwise. The TanStack wave published those 84 malicious versions with valid SLSA provenance attestations, using short-lived OIDC tokens stolen from the project's own CI. The identity was legitimate; the runner holding it was compromised.

OIDC shrinks the window in which a stolen credential is useful from months to minutes, but a secret that lives for minutes inside a compromised build environment is still a secret an attacker can use right now.

So treat trusted publishing as the foundation and add the layers that assume the runner itself is hostile:

  • Scope publishers to environments, not just repos. A publisher pinned to a production environment with required reviewers means a compromised pull-request build cannot mint a publish-capable identity at all.
  • Run high-value packages stage-only. The human 2FA approval gate exists precisely for the TanStack scenario: even a fully valid CI identity can only queue the release, and a person reviews it before it goes public.
  • Pin the workflow filename and audit changes to it. The publisher config trusts one file; a malicious edit to that file is a publish-capable event. CODEOWNERS on the workflow path plus required review turns that edit into something visible.
  • Keep the provenance habit. Attestations did not stop the TanStack publishes, but they made the malicious versions identifiable after the fact — which versions, from which run, at which commit. Detection speed is the consolation prize that compounds.

None of this argues against migrating. It argues against migrating and declaring victory. The token deletion is the win; the layers are what keep the win.

The lesson for your own build pipeline

Everything above is about publishing JavaScript packages, but the underlying pattern — long-lived credentials sitting in automation, exchanged for short-lived identity bound to the workload — generalizes to every secret in a git-push PaaS build pipeline. Most self-hosted platforms still default to the left column:

Long-lived secretWhat steals it buysShort-lived replacement
Registry push token on the builderPush arbitrary images as your platformWorkload identity (SPIFFE, cloud instance identity, or OIDC) minted per build, scoped to one repo
Static deploy key on the git-clone stepRead (and sometimes write) every repo the key touches, foreverPer-build clone token issued at job dispatch, expired when the build ends
Provider API key in the control planeFull infrastructure access on exfiltrationOIDC federation between the control plane and the provider, with per-operation audience claims
CI publish token for releasesSilent malicious releases, the 2026 patternTrusted publishing or stage-only flows with human 2FA approval

The npm story is useful here precisely because it is finished: a major registry walked the whole path from "tokens everywhere" through "federated identity" to "human gate for the last mile," with dated enforcement milestones the ecosystem had to meet. A self-hosted platform does not need to invent this sequence — it needs to copy it, starting with an inventory of every credential a build touches and ending with none of them surviving longer than the build itself.

Close the lever

The 2026 waves were not sophistication contests. Attackers did not break npm's cryptography; they picked up valid credentials and published, the same way the legitimate pipeline does. Trusted publishing removes the standing credential, staged publishing adds the human gate, and the January 2027 deadline removes the excuse. Migrate the next package you touch: ten lines of YAML, one registration screen, one deleted secret — and one fewer lever for the next wave to pull.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex