In March 2026, a supply chain worm spread across 47 npm packages in under 72 hours. In April, the Mini Shai-Hulud campaign used stolen OIDC tokens to backdoor TanStack and Mistral AI packages before any human noticed. In both cases, the attack moved faster than any manual review could catch — because there was no manual review. Packages published instantly. That just changed.
TL;DR
- npm packages no longer publish directly to the registry — they enter a staging queue first
- A human maintainer must approve via 2FA before the package becomes installable
- Available from npm CLI 11.15.0 with
npm stage publish- Three new install-time flags let you block non-registry dependency sources
- npm CLI v12 will make
--allow-gitstrict by default
Why This Matters
If your project uses npm packages — and it almost certainly does — this change affects how attackers can weaponize the packages you depend on.
Supply chain attacks work by hijacking the publication step: a compromised CI/CD token, a stolen maintainer account, or a dependency confusion trick allows a malicious version to land in the registry before anyone reacts. From there, the next npm install in any downstream project picks up the poisoned package automatically.
Staged publishing breaks this loop by inserting a human checkpoint. An automated pipeline can’t silently push malicious code anymore — it needs a real person with a working second factor to approve.
The Old Problem: Publish Is Instant
The traditional npm publish command works like this:
- CI pipeline runs, builds the package
npm publishuploads it directly to the registry- Package is immediately available to every
npm installworldwide
This is fast and convenient. It’s also exactly what attackers exploit. The Axios attack in March 2026 hit both release branches within 39 minutes and was downloaded by real users before the maintainers even noticed. The attack surface was the gap between “CI pipeline triggered” and “human reviews the artifact.”
The New Flow: Staged Publishing
GitHub’s staged publishing adds a queue between the pipeline and the registry.
CI pipeline → npm stage publish → [staging queue] → maintainer approves (2FA) → registryInstead of going live immediately, the prebuilt tarball sits in a staging area. The package maintainer gets notified, reviews the release, and approves it by passing a 2FA challenge. Only then does the version become installable.
This means a compromised CI token, a stolen OIDC credential, or a malicious workflow can upload to the staging queue — but it cannot publish to the world without a human in the approval seat.
How to Enable It
Update to npm CLI 11.15.0 or newer:
npm install -g npm@latestThen replace npm publish with npm stage publish in your CI configuration:
# GitHub Actions example- name: Stage npm package run: npm stage publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}The maintainer then approves via the npmjs.com dashboard after passing a 2FA challenge.
Pairing With Trusted Publishing (OIDC)
Staged publishing works best when combined with trusted publishing — npm’s OIDC-based authentication that eliminates long-lived access tokens entirely.
With OIDC, your GitHub Actions workflow proves its identity to npm using a short-lived token signed by GitHub. No NPM_TOKEN secret needed. No credential that can be stolen from your repository settings.
The combination looks like this:
| Layer | What It Does |
|---|---|
| Trusted publishing (OIDC) | Removes long-lived credentials from CI |
| Staged publishing | Blocks automated live releases |
| 2FA approval gate | Requires human verification for every release |
You can enforce this even harder with a “stage-only” policy on your npm package — this setting rejects standard npm publish commands entirely. Anyone (or any pipeline) that tries to bypass the staging queue gets blocked automatically.
New Install-Time Flags
Alongside staged publishing, npm 11.15.0 introduces four flags that control what dependency sources npm install will accept. Think of them as a firewall for your dependency graph.
| Flag | Controls |
|---|---|
--allow-file | Local file paths and tarballs |
--allow-remote | Remote HTTPS URLs |
--allow-directory | Local directory installs |
--allow-git | Git repository installs |
Each flag accepts either all (current default — everything allowed) or none (block completely). You set these in .npmrc or package.json.
Why This Matters for Defense
Non-registry install sources are a common supply chain attack vector. Dependency confusion attacks work by registering a public package with the same name as an internal package — the installer then pulls the attacker’s version from npm instead of the internal one. Git-based dependencies are another weak point: a compromised GitHub repo can push malicious code that gets pulled in at install time.
Locking these sources down is straightforward:
# .npmrc — block all non-registry sources except what you explicitly needallow-git=noneallow-remote=noneallow-file=noneFor most projects, registry packages are all you need. Blocking everything else removes entire categories of attack surface without any code changes.
What Changes in npm v12
The --allow-git flag currently defaults to all — meaning Git repository dependencies work out of the box. In npm CLI version 12, this default flips to none.
If your project has any git+https:// or github:user/repo entries in package.json, they will break on upgrade. Audit your dependency list now:
# Find git-sourced dependencies before v12 shipsgrep -E '"git\+|"github:|"bitbucket:|"gitlab:' package.json package-lock.jsonIf you find any, replace them with versioned registry packages where possible. If you genuinely need a git source, you’ll need to explicitly set allow-git=all — but that’s a decision you’ll make consciously, not by accident.
What You Can Do Today
If you maintain an npm package:
- Update to npm CLI 11.15.0:
npm install -g npm@latest - Switch CI publish step from
npm publishtonpm stage publish - Enable trusted publishing (OIDC) on npmjs.com to eliminate token secrets
- Enable the “stage-only” enforcement policy on your package
If you install npm packages:
- Add
allow-git=noneandallow-remote=noneto your project.npmrc - Run
grep -E '"git\+|"github:' package.jsonto find any git dependencies - Pin your dependencies to exact versions — ranges let attackers slide in new versions
- Consider running
npm auditin CI as a blocking step
For teams:
- Review your CI/CD pipelines for long-lived npm tokens — replace with OIDC where possible
- Set up alerts for new package versions published by packages you depend on
- Test your install with strict flags enabled in a staging environment before rolling to production
Related Posts
- The Package You Trusted: How the Axios Supply Chain Attack Happened — the March 2026 attack that hit 400M monthly downloads; the exact scenario staged publishing would have blocked
- Shai-Hulud: The Open-Source GitHub Actions Token Harvester — how stolen OIDC tokens are weaponized in supply chain attacks
- The Build Is the Target: CI/CD Pipeline Attacks and How to Detect Them — broader detection playbook for CI/CD pipeline compromise
- We Built a Supply Chain Scanner — Here’s What We Learned — complementary tool-based defense for catching suspicious packages
Sources
- Staged publishing and new install-time controls for npm — GitHub Changelog
- npm Adds 2FA-Gated Publishing and Package Install Controls Against Supply Chain Attacks — The Hacker News
- GitHub Adds Staged Publishing to npm to Block Automated Supply Chain Attacks — Cyber Security News
- Self-Propagating Supply Chain Worm Hijacks npm Packages — The Hacker News
- Axios Supply Chain Attack Pushes Cross-Platform RAT via Compromised npm Account — The Hacker News