The short answer: the defaults changed under you. npm v12, announced in June 2026 and landing in July, stops running dependency install scripts unless you approve them — closing the exact path the Shai-Hulud worm used. All four package managers now support a minimum release age, which filters most attacks because bad releases get pulled within hours. And every npm classic publish token was permanently revoked on 9 December 2025. Two of those changes will break builds before they protect anyone, which is why teams should check now rather than during an upgrade. On this site's own repository the check took a minute: 28 direct dependencies, 471 installed packages, one install script.
A supply chain attack here means someone gets malicious code into a package you already trust, rather than breaking into your servers. An install script is the part that makes it work: a preinstall, install or postinstall entry in a package's package.json that npm runs automatically when the package is installed.
That code runs as your user, with your environment variables and your credentials, before anything is imported or reviewed. It runs on your laptop and it runs in CI. That is the whole vulnerability, and 2026 was the year the tooling finally did something about it.
What actually happened
Three incidents are worth knowing, because each one shows a different way in.
Shai-Hulud, and then Shai-Hulud again. The first worm hit npm on 14 September 2025 through compromised maintainer accounts. A second wave, self-described as "The Second Coming", uploaded compromised versions between 21 and 23 November 2025 and was detected on the 24th. It reached more than 700 packages — one analysis counted 796 — and created over 27,000 attacker-controlled GitHub repositories to exfiltrate to. It executed at the preinstall phase, scanned for GitHub, npm, AWS, GCP and Azure credentials, and then republished itself using the npm tokens it stole. That last part is what made it a worm rather than an incident.
Dependency confusion, at scale. Microsoft Threat Intelligence documented 33 malicious packages published in two bursts on 28 and 29 May 2026, across nine organisational scopes chosen to mirror real internal corporate namespaces. Several spoofed internal GitHub Enterprise and Jira URLs in their package.json to look legitimate to anyone skimming. Once installed they pulled an obfuscated reconnaissance payload from a command-and-control server.
Red Hat, through an editor extension. On 29 May 2026 an attacker compromised 32 packages in the @redhat-cloud-services namespace. The entry point was not npm. It was a GitHub account taken over through a malicious VS Code extension, which then injected code into repositories the developer maintained. Red Hat confirmed no shipped products contained a compromised version.
That third one deserves a moment. This is a company with a real security organisation, and the path in was a developer's editor. The perimeter is a laptop.
What a dependency tree actually looks like
Abstract risk is easy to ignore, so here is a measured one. This site is a Next.js application. Running the numbers on its own repository today:
- 28 direct dependencies — 20 runtime, 8 development.
- 471 packages actually installed, from the committed lockfile.
- One of those 471 declares an install script:
sharp, the image library.
Two things follow. The first is uncomfortable: choosing 28 things means trusting 471, a roughly seventeen-fold expansion, and no one reviews the other 443. The second is reassuring, and it is the part the coverage keeps missing. If install scripts were everywhere, turning them off would be a catastrophe. They are not. In a normal application tree the list of packages that need approval is short enough to read.
I would encourage you to measure your own rather than trust mine. It is the single most useful minute you can spend on this.
The defaults changed underneath you
Two shifts, both landing in 2026, both aimed squarely at the mechanism above.
npm v12 turns install scripts off. GitHub announced the breaking changes on 9 June 2026 and shipped them in July. Three defaults changed:
| What changed | What it breaks | How to prepare |
|---|---|---|
preinstall, install and postinstall from dependencies no longer run | Native builds, and anything relying on a package's setup step | Run npm approve-scripts --allow-scripts-pending, approve what you trust, commit the result |
The implicit node-gyp rebuild is also blocked | A package with a binding.gyp and no declared script still fails | Same approval list — it will appear there |
--allow-git defaults to none, and remote-URL tarballs need --allow-remote | Git dependencies and https tarballs, direct or transitive, stop resolving | Move them to the registry, or allow them explicitly |
All of it is available behind warnings from npm 11.16.0, so you can find out what breaks before you are forced to. Worth checking which npm you are actually on — this environment was still on 10.9.7, which does not warn at all.
Every package manager grew a cooldown. A minimum release age refuses to install a version published more recently than a threshold you set. It works for an unglamorous reason: compromised releases are usually flagged and pulled from the registry within hours, so a short delay filters most of them with no scanning and no monitoring.
| Package manager | Setting | Unit | Available since | Default |
|---|---|---|---|---|
| npm | release-age gate | days | CLI 11.10.0 (February 2026) | off |
| pnpm | minimumReleaseAge | minutes | 10.16 (September 2025) | 1440 (24h) since pnpm 11.0 |
| Yarn | npmMinimalAgeGate | minutes | Berry 4.10.0 | off |
| Bun | minimumReleaseAge | seconds | 1.3.0 | off |
Four names, four units, one idea. pnpm is the only one that turns it on for you. Add an exclusion list so an urgent security patch can still land the day it ships.
Your publish tokens already expired
This part only bites if you publish packages, but it bites hard and it already happened.
npm disabled classic token creation on 5 November 2025 and permanently revoked every classic token on 9 December 2025. They cannot be used, recreated or recovered. Pipelines that had one hardcoded did not degrade — they stopped.
What replaced them is stricter on purpose. Granular write tokens now default to a 7-day expiry, down from 30, with a 90-day maximum where it used to be unlimited. The option to bypass two-factor authentication for local publishing is gone, and was restricted further on 31 July 2026. Local login sessions use two-hour tokens.
The better answer is to stop holding a secret at all. Trusted publishing uses OpenID Connect to issue a short-lived credential to a specific CI job, so there is no stored token to steal — which matters when the last worm spread precisely by stealing stored tokens. It works today with GitHub Actions and GitLab CI/CD, with other providers in progress.
What to actually do
- Count your tree. Read
package-lock.json, not the filesystem, and list the packages declaringpreinstall,installorpostinstall. Count against lockfile entries — a filesystem scan picks up test fixtures nested inside other packages and inflates the number. - Check your npm version. Anything below 11.16.0 gives you no warning about the v12 defaults. Upgrade the CLI before you upgrade the major.
- Build the approval list deliberately. Run
npm approve-scripts --allow-scripts-pending, approve only what you recognise, and commit the result so it is reviewed like code. - Turn on a release-age gate. 24 hours is the sensible default, with an exclusion list for urgent patches. On pnpm 11 you already have it.
- Kill every long-lived publish token. If you publish, move to trusted publishing. If you cannot yet, rotate to granular tokens with the shortest workable expiry and remove the rest.
- Treat the laptop as production. The Red Hat compromise arrived through an editor extension. Audit what your editor runs, and keep cloud credentials out of shells that run
npm install.
Common questions
Does this reach me if I only build applications and never publish packages?
Yes, for the install-time half. Any npm install runs dependency install scripts with your credentials, so a compromised release reaches your laptop and your CI regardless of whether you publish anything. The token and publishing changes only matter if you push packages to the registry.
Will upgrading to npm v12 break my build? It might, and that is the reason to check early rather than during an upgrade. It blocks dependency install scripts, git dependencies and remote tarballs by default. The fix is an approval list, which is usually short — on this site's 471-package tree, exactly one package needed approving.
Isn't npm audit already handling this?
No. npm audit compares your installed versions against published advisories, so it finds known problems in known versions. A malicious release published an hour ago is neither. It is a useful hygiene tool and a poor supply-chain defence, and the two get confused constantly.
What is trusted publishing and is it worth the migration? It uses OpenID Connect so your CI job receives a short-lived credential scoped to that job instead of you storing a long-lived npm token. It is worth it: stored tokens are exactly what the Shai-Hulud worm harvested to spread. It supports GitHub Actions and GitLab CI/CD today.
Does a 24-hour cooldown actually stop anything? It stops most of it, because compromised versions are typically detected and pulled from the registry within hours of publication. A cooldown converts that industry-wide response time into your own protection without you running any scanner. It is the highest-value single setting on this list.
The honest summary
The uncomfortable framing first: none of this is a vulnerability in your code, and none of it is fixed by writing better code. You inherited an execution path, and until this year it was on by default.
The reassuring part is that 2026's changes are unusually well aimed. Install scripts off by default removes the mechanism. Release-age gates remove the window. Trusted publishing removes the credential. Those three cover most of what actually happened over the past year, and none of them requires buying anything.
What they do require is someone to own the decision. In most companies between one and twenty million in revenue, dependency risk belongs to nobody in particular — it is everyone's problem in the abstract and no one's task on a Monday. That is the real finding, and it will not be solved by a scanner.
So: measure your tree this week, check your npm version, build the approval list, turn on a cooldown, and retire your long-lived tokens. If you would rather that were somebody's job, web architecture consulting is where I do this kind of work, and for a smaller site the equivalent is a maintained website care plan rather than a dependency tree nobody has opened in two years. For the neighbouring problems, AI coding agents in a production codebase covers what happens when a model adds dependencies you did not choose, MCP in production covers the same "the protocol will not save you" lesson one layer up, and a technical debt audit is the wider version of taking inventory before something forces you to.
Sources: Upcoming breaking changes for npm v12 (GitHub Changelog, 9 June 2026) · Our plan for a more secure npm supply chain (GitHub Blog) · Malicious npm packages abuse dependency confusion (Microsoft Security Blog, 29 May 2026) · RHSB-2026-006: Supply chain compromise of @redhat-cloud-services npm packages (Red Hat) · Shai-Hulud worm compromises npm ecosystem (Unit 42) · pnpm 11.0 release notes
Written by Jagatjeet — Jagatjeet (jagatjeet.com) is a web design, local SEO and AI automation studio in Kamloops, British Columbia, serving the Thompson-Okanagan and BC Interior. Published 2 September 2026. Last updated 2 September 2026. Dependency counts were measured on this site's own repository on the publication date. Package manager defaults and registry policies change quickly — check the linked primary sources before acting on anything time-sensitive here.