npm Breaks Its Oldest Promise to Kill the Worm
GitHub's new defaults across npm and Actions attack every stage of the self-replicating supply chain playbook.
For fifteen years, npm install has meant "run whatever code the registry hands you, immediately, with your credentials in scope." That contract just ended. In a recap of the past few months of hardening work, GitHub laid out a series of changes across npm and GitHub Actions that, taken together, amount to the most aggressive redesign of the JavaScript supply chain since scoped packages. The headline item: npm v12 stops running install scripts from dependencies by default. That's a breaking change to npm's oldest behavior, and it's the right call about three years too late.
The context matters. The Shai-Hulud worm in late 2025 showed that npm compromises had become self-replicating: steal a maintainer token, publish a malicious version with a postinstall script, harvest credentials from every machine that installs it, use those credentials to publish more malicious versions. Roughly 500 packages fell in the first wave, hundreds more in the November follow-up. This year the pattern industrialized — the group Google Threat Intelligence tracks as UNC6780 (publicly "TeamPCP") has reportedly run successive waves of compromises spanning npm, PyPI, and CI systems. Advisories and scanning were never going to stop a loop that executes in minutes. Only breaking the loop's mechanics would.
Each change targets a specific link in the chain
What makes this batch of changes coherent — and why I'd call it a genuine shift rather than security theater — is that each one maps to a distinct stage of the worm lifecycle.
Account takeover. High-impact npm accounts now enter a 72-hour read-only mode after an email change or 2FA recovery, which converts "phished maintainer" from an instant registry compromise into a three-day race the defender usually wins. The chalk/debug incident in September 2025 went from phishing email to poisoned packages with billions of weekly downloads in under an hour; this closes that window.
Credential theft in CI. actions/checkout now refuses to check out untrusted fork code under risky triggers like pull_request_target by default — finally killing the "pwn request" pattern security researchers have been writing up since 2021. Actions caches are read-only from untrusted triggers, cutting off cache-poisoning as a privilege-escalation route into release workflows. And a network firewall for Actions runners (technical preview) logs outbound traffic, with egress blocking planned — which is the feature that would have actually caught Shai-Hulud's exfiltration in the act.
Publishing with stolen tokens. This is the structural one. Staged publishing (npm CLI 11.15.0+) turns npm publish into a two-step: CI uploads a tarball to a stage queue, and a human with 2FA approves it before it becomes installable. Paired with trusted publishing in stage-only mode, your pipeline literally cannot make a package live — the OIDC credential is only good for staging. A stolen CI token becomes a stolen ability to propose a release. Granular access tokens are on a deprecation path for publishing too: per independent reporting, they lose sensitive account operations in August 2026 and direct publish rights in January 2027.
Spreading on install. npm v12 disables preinstall/install/postinstall scripts from dependencies, plus git and remote-URL dependencies, unless you allowlist them explicitly and commit the allowlist. Install-time execution was the worm's ignition system; it's now opt-in.
Downstream blast radius. Dependabot version updates now wait three days by default before opening PRs. Most malicious releases in the 2025–26 wave were detected and yanked within 24–48 hours, so a 72-hour cooldown means auto-update bots stop being the attacker's distribution network. Security updates still land immediately.
npm is the last one through the door
Worth being honest about: little of this is novel. pnpm made dependency install scripts opt-in in v10, back in January 2025, as a direct response to the attack wave. Bun has required trustedDependencies for lifecycle scripts since before that. PyPI shipped trusted publishing in 2023; npm followed in mid-2025. Even the Dependabot cooldown is Renovate's minimumReleaseAge with the serial numbers filed off, and Debian has separated "uploaded" from "trusted" for decades.
But npm's defaults are the ones that matter, because npm is where the installed base lives. pnpm protecting its users never protected the ecosystem; npm changing install semantics protects everyone who upgrades, whether they think about security or not. Defaults are the product.
What you should actually do
If you maintain packages: get on npm CLI 11.15.0 or later, enable staged publishing, and move CI to trusted publishing in stage-only mode. Delete every long-lived npm token in your CI secrets — after January 2027 they won't publish anyway.
If you consume packages: before npm 12 hits your team, run an audit of which dependencies genuinely need install scripts (for most apps it's a short list — native modules like esbuild, sharp, node-gyp builds) and build the allowlist deliberately rather than in a panic when installs break. Leave the Dependabot cooldown on. If you're on Actions, audit every workflow using pull_request_target or workflow_run — the new checkout default reduces the risk, but least-privilege permissions: blocks and workflow execution policies are still on you.
Where this still leaks
Three gaps keep me from calling this solved. First, allowlists rot into rituals: the first time a build breaks, someone will approve the pending scripts wholesale, and a compromised esbuild lands in the allowlist same as a clean one. The mechanism moves trust; it doesn't create it. Second, staged publishing reintroduces a human bottleneck that high-velocity teams will resent, and the history of security friction says some of them will find the workaround before they find the threat model. Third, none of this touches the actual root cause — that a typical Node app trusts a thousand transitive maintainers — and runtime code from a compromised package still executes with full authority once you require it. Install scripts were the fastest payload, not the only one.
Still, the economics have genuinely changed. Every stage of the worm lifecycle now has a default-on brake: takeover has a cooldown, CI tokens can't publish, installs don't execute, updaters wait out the detection window. Attacks that needed minutes now need days and a human mistake. That won't end npm supply chain attacks — but it ends the era when one phished maintainer could compromise half the ecosystem before lunch.
Sources & further reading
- Disrupting supply chain attacks on npm and GitHub Actions — github.blog
- npm Adds 2FA-Gated Publishing and Package Install Controls Against Supply Chain Attacks — thehackernews.com
- npm 12 Disables Install Scripts by Default to Reduce Supply Chain Risk — thehackernews.com
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 0
No comments yet
Be the first to weigh in.