Open Source Security

npm Mandatory 2FA for Publishing: How the November 2025 Rollout Hardened the Registry

After the Shai-Hulud worm compromised more than 500 npm packages in September 2025, GitHub published a revised timeline forcing FIDO 2FA, 90-day token caps, and disabled token publishing by default. Here is the defender view.

Shadab Khan
Security Engineer
6 min read

On November 5, 2025, GitHub published a revised npm security timeline in community discussion #178140 after public feedback on the original September proposal. The revision came directly after the Shai-Hulud worm, which between September 8 and November had compromised more than 500 packages including chalk, debug, @ctrl/tinycolor, and the ngx-bootstrap line, with combined weekly downloads exceeding two billion. From a defender and registry-operator perspective, the policy shift represents the most significant change to the npm publishing trust model since granular access tokens shipped in 2022. This post walks through what the registry changed, how the rollout has unfolded into early 2026, and what verification a consuming organization should now bake into CI.

What did npm change in the publishing trust model?

The revised timeline lists five concrete changes that operators have begun enforcing in waves. First, classic tokens are being deprecated; new ones can no longer be created and existing ones are scheduled for revocation. Second, TOTP-based 2FA is being phased out in favor of FIDO2/WebAuthn-based 2FA, which is resistant to the phishing pattern used in the September 8 chalk/debug maintainer compromise. Third, granular write tokens with publishing permission are now capped at a 90-day maximum lifetime, with any token previously set to expire after February 3, 2026 having had its expiration force-adjusted to that date. Fourth, new write tokens enforce 2FA by default for the publish step, with an opt-out preserved temporarily for build-system backward compatibility. Fifth, the option to bypass 2FA for local npm publish has been removed entirely. These changes do not by themselves prevent malware authorship, but they collapse the success rate of credential-theft worm propagation, which was Shai-Hulud's defining capability.

How was the rollout coordinated with the ecosystem?

The rollout has been deliberately phased to avoid the kind of overnight CI breakage that derailed earlier 2FA pushes in 2022. GitHub announced the schedule on the npm blog and in community discussion #178140, gave maintainers a 90-day window before any token cap took effect, and worked with high-traffic publishers, including the Node.js core, Yarn, pnpm, and TanStack teams, to migrate to Trusted Publishing first. The OpenSSF Securing Software Repositories WG ran a public office-hours session in December 2025 to help maintainers understand how the new defaults interact with existing GitHub Actions workflows. The Python Package Index team also coordinated by publishing a parallel advisory on November 26, 2025, noting that PyPI credentials had been found leaked inside Shai-Hulud-compromised GitHub repositories and were revoked even though no abuse had been observed.

What provenance and authentication signals exist now that did not before?

Three signals are now visible to consumers that were not in early 2025. The first is the npm package view on the registry website, which surfaces whether a release was published via Trusted Publishing (OIDC) versus a long-lived token. The second is the public Sigstore provenance attestation, attached automatically when publishing through a Trusted Publisher, that proves the workflow file, repository, and ref that produced the tarball. The third is the per-token policy metadata returned by the registry API for token-published versions, including the token type and 2FA status at time of publish. Together these let downstream organizations write policy gates that, for example, refuse to install a new major version of a critical package unless it carries provenance from the maintainer's known GitHub org.

How do you verify the new posture in CI?

A consuming pipeline can verify both that a package shipped with provenance and that the provenance points where you expect. The npm CLI ships npm audit signatures, which checks Sigstore attestations for all packages in your lockfile against the public-good Rekor instance.

# Verify provenance signatures for every package in the lockfile
npm install --ignore-scripts
npm audit signatures

# Inspect the published provenance for a specific version
npm view chalk@5.4.1 dist.attestations

For deeper checks, the GitHub attest-verify workflow and the standalone cosign verify-blob-attestation command can be wired into CI to enforce a policy like "only allow installs of packages whose provenance points at the official upstream GitHub org and a tagged release workflow."

cosign verify-blob-attestation \
  --bundle attestation.sigstore.json \
  --new-bundle-format \
  --certificate-identity-regexp "^https://github.com/upstream-org/.+" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --type "https://slsa.dev/provenance/v1" \
  package-5.4.1.tgz

A useful operator practice is to require this check only for the small set of "blast-radius" dependencies (the lodash, axios, chalk, debug tier) rather than every transitive package, since the cost-benefit dramatically changes for less-downloaded leaves.

What policy gate catches the next Shai-Hulud-class worm going forward?

The Shai-Hulud worm propagated by reading credentials out of compromised CI environments and republishing infected versions through whatever publishing pathway was still allowed. Three policy gates close that loop. Gate one is "deny any install of a new version of a critical package within the first N hours of publish unless it carries valid Trusted Publishing provenance," giving the registry's quarantine flow time to fire. Gate two is "deny any postinstall script in transitive dependencies that fetches outbound network resources during install," which the npm --ignore-scripts flag enforces globally if your tooling can tolerate it. Gate three is "alert on any package whose publishing token was created after a known compromise window," which can be evaluated against the registry's publish metadata. The September 2025 wave was removed within roughly 2.5 hours of report; the November Shai-Hulud 2.0 wave took roughly 12 hours. For most enterprises, a deny-on-fresh-publish window of 24 to 72 hours sits inside both response windows without blocking legitimate releases.

What still has to mature for this posture to hold?

Three gaps remain visible to defenders. The first is build-system backward compatibility: some self-hosted CI runners still cannot present an OIDC token to npm and currently rely on the 2FA bypass that GitHub plans to remove. Working through those last holdouts is the bulk of the 2026 rollout work. The second is downstream policy adoption. Sigstore provenance covers a growing share of the top 360 npm packages, but the long tail is far behind, and the most-attacked packages in the September wave were tier-two utilities, not the headline names. The third is signal quality across mirrors. Many organizations consume npm via Artifactory or Nexus proxies that historically did not surface the provenance attestation back to internal consumers. Both vendors shipped updates in late 2025 to pass attestations through, but auditing whether your mirror does is non-trivial.

How Safeguard Helps

Safeguard treats package publishing signals as first-class inputs to deployment policy. Its provenance verification engine ingests Sigstore attestations from npm, PyPI, RubyGems, crates.io, and NuGet and enforces per-org rules such as "require provenance for any new install of a top-1000 package," with results surfaced as policy-gate evaluations alongside CVSS findings. The malicious-package feed cross-references the registry quarantine streams and OpenSSF malicious-packages repository so a Shai-Hulud-style wave shows up inside dependency reviews within minutes. Postinstall-script audit highlights every install hook in your lockfile and lets you block on network egress during install, which would have stopped Shai-Hulud's worm propagation in its tracks. Pinning policy enforces sha-pinned references for GitHub Actions, semver-major freezes for selected critical npm packages, and 2FA-method gating that refuses any version published via TOTP after a configurable cutoff date, giving your CI a deterministic answer to "are we affected this time?"

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.