npm Is on Fire: Why the Architecture Is the Product
Wire Fire: Episode 01 npm (the open registry that nearly every JavaScript project on Earth depends on) has been under permanent attack for years. This is not a recent shift in adversary attention. It is a slow, observed, well-documented escalation that the ecosystem has not architecturally answered. The headline number: in 2025 alone, 454,648 malicious packages were published to the npm registry. Over 99 percent of all open-source malware now targets npm. The remaining 1 percent covers every other registry combined (PyPI, RubyGems, Maven Central, NuGet, Cargo, Composer). If you have ever installed a JavaScript dependency, you have participated in an ecosystem whose security model is, in the most polite possible terms, an act of structural optimism. This post is a Wire Fire sitrep, the first episode of a new series for active security incidents. It covers the six weeks between 31 March and 14 May 2026, and places that evidence inside the larger structural story it belongs to. Four named incidents hit the registry in this window. Each is documented, each is attributable, and each demonstrates the same failure: there is no brake in the pipeline, and there never was one. A North Korean state-sponsored group, tracked by Microsoft as Sapphire Sleet (Google Mandiant identifies the cluster as UNC1069), published two malicious versions of axios to the npm registry: 1.14.1 and 0.30.4. Both were tagged "latest", the default release channel that the majority of consumers follow without thinking. The mechanism was indirect. The malicious versions declared a poisoned dependency, [email protected], whose postinstall lifecycle hook downloaded and executed a cross-platform remote-access trojan on the host machine. macOS, Windows and Linux were all targeted by the same payload, fetched at install time from attacker-controlled infrastructure. axios receives roughly 100 million weekly downloads. The malicious versions were live for approximately three hours before being pulled. Three hours of "latest", for a library at that scale, is enough to compromise a meaningful percentage of every CI build, every developer workstation, and every container build that touched a fresh npm install in that window. CISA issued a cybersecurity alert on 20 April 2026. Microsoft published a detailed analysis on 1 April 2026. A worm variant tracked as "Mini Shai-Hulud" (a smaller, more targeted descendant of the original Shai-Hulud worm campaigns of 2025) infected four SAP-related npm packages. The payload was credential-harvesting: tokens, environment variables, SSH keys, anything reachable from the compromised install host. The packages were used in SAP-adjacent JavaScript tooling, narrow in audience but deep in privilege. At 19:20 UTC on Sunday 11 May, 84 malicious versions of 42 packages from the @tanstack/* namespace were published in a six-minute window. TanStack is a popular JavaScript library suite; @tanstack/react-router alone has roughly 12 million weekly downloads. Within 48 hours the campaign expanded to 172 packages and 403 versions, across both npm and the Python registry PyPI. Cumulative downloads of the compromised packages amounted to roughly 518 million over the affected version windows. Other namespaces hit in the same wave: @uipath/*, @mistralai/mistralai, OpenSearch, Guardrails AI. The technical mechanism is worth looking at closely, because it explains how a single compromise becomes mass-published code. On Monday 12 May, security research collective vx-underground reported that the full Shai-Hulud worm source code had been published openly. The attack toolchain (cache poisoning, OIDC extraction, provenance-attested publishing under stolen identities) is no longer the exclusive property of any single threat actor. It is off the shelf. This is the watershed of the six weeks. Before 12 May, the worm was attributable to specific groups (TeamPCP, with documented overlap to nation-state actors). After 12 May, anyone with a misconfigured CI/CD pipeline and the will to use it can deploy the same toolchain against any package with similar exposure. The name is a Dune reference. Frank Herbert's giant sandworms, the predators of the desert planet Arrakis, are called by the Fremen "Shai-Hulud" ("the Old Man of the Desert"). The first npm worm to bear the name appeared in September 2025; it has hit the ecosystem in four waves to date. Wave Date Scope 1 September 2025 ~500 packages compromised in the first self-replicating wave 2 November 2025 "Shai-Hulud 2.0"; expanded scope, ~25,000 GitHub repositories affected 3 December 2025 Modified payload test, narrower scope 4 April–May 2026 Mini Shai-Hulud variant, SAP namespace, TanStack wave, source-code release Each wave taught the attackers something the previous wave did not. By wave four, the operation could publish provenance-attested packages from stolen identities, scale across CI/CD environments in seconds, and survive into legitimate downstream builds via cache poisoning. The waves are no longer waves. They are weather. Modern software is built on trust between strangers. When you install a JavaScript package, you do not install one thing; you install whatever that package declares as its dependencies, plus whatever those declare, plus whatever those declare. The average modern JavaScript project ends up with over a thousand transitive contributors no one on the team has ever met. Every install runs whatever code the package author wrote, with the user's full operating-system permissions. The npm postinstall lifecycle hook is, by design, a script that runs arbitrary code on the install host immediately after a package is unpacked. It exists for legitimate reasons (compiling native extensions, fetching platform-specific binaries), and for that reason it cannot simply be removed without breaking large parts of the ecosystem. This is the structural attack surface. Compromise one developer account anywhere in the dependency tree, and the worm can: Steal credentials from every machine that runs the next install. Use those credentials to publish new poisoned versions under the stolen identity. Propagate to every project that depends on those packages. Move on. The 11 May TanStack wave automated this entire cycle. The technical chain, for specialists, exploited three weaknesses in GitHub's standard build service that combine to devastating effect: # Sketch of the misconfiguration class the worm exploited on: pull_request_target: types: [opened, synchronize] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} - run: pnpm install The pull_request_target event executes the workflow in the base repository's security context, including its secrets, even when the pull request originates from a fork. Combined with the GitHub Actions cache, which is shared across the fork-base trust boundary, a poisoned pnpm store survives into the legitimate build of the next merge. The OIDC token the runner uses for npm publishing sits in process memory long enough to be extracted at runtime. Combine the three weaknesses and you can publish provenance-attested packages from a stolen identity, on someone else's CI infrastructure. Provenance attestation, the supply-chain integrity feature npm introduced to address exactly this class of attack, gets converted into the attack vector itself. The response depends on your role. If you or your team installed any of the following packages since 31 March 2026: axios versions 1.14.1 or 0.30.4 Any package in @tanstack/* Any package in @uipath/* @mistralai/mistralai Any SAP-related npm package then assume the install host is compromised. Rotate every credential reachable from that machine: cloud API tokens, SSH keys, npm publish tokens, GitHub PATs, browser-stored secrets, the lot. Downgrade the affected packages to a known-clean version. On FreeBSD, check VuXML for the current advisory status of the ports you maintain. For new installs, in order of impact: Set npm config set ignore-scripts true globally. This stops postinstall and other lifecycle hooks from running arbitrary code at install time. It will break a small number of packages that genuinely need install-time compilation; those can be opted back in case by case. Pin transitive dependencies via a lockfile and review changes to that lockfile in pull requests as if they were code, because they are. Run untrusted installs in isolation. On FreeBSD, a jail is the right primitive. On other systems, an unprivileged container or a virtual machine is the equivalent. Treat any package with more than a hundred transitive maintainers as if you were accepting code from a hundred strangers, because you are. The lockfile is now an audit artefact. Diff it on every change. Build environments that touch npm should be ephemeral, isolated, and incapable of reaching production credentials. The OIDC token model needs adjustment: scope publish tokens to specific packages, rotate aggressively, and never let an interactive CI step coexist with publish credentials in the same job. Add pkg audit (FreeBSD) or the equivalent vulnerability-feed query for your platform to the CI pipeline. A daily diff against the appropriate VuXML / OSV / GHSA feed is a small habit with disproportionate value. This is the harder section to write without sounding partisan, so the structural verdict will do the work. npm is not safe by default. The architecture is the product. Every install runs arbitrary code with user permissions. The registry signs nothing the consumer is required to check. Five years of public incidents and a public worm have not changed any of this, because changing it would require breaking the ecosystem the product is built on. Budget for that. Either pay the runtime cost of isolation, the ongoing cost of audit, and the human cost of credential rotation hygiene; or pick a registry that takes its security architecture seriously by design. There are no other choices that survive contact with the four waves of evidence above. FreeBSD's pkg system answers a different question than npm does. Where npm optimises for distribution speed and contributor accessibility, pkg optimises for review and auditability. Each FreeBSD port has a named human maintainer. Every change to a port is reviewed by a committer before merge. Every binary package is built reproducibly from a signed Makefile recipe in the Ports tree. The pkg client verifies a signature on the package set it downloads. The whole pipeline is slow on purpose and boring on purpose, and that is what a supply chain looks like when the design assumes some packages will, eventually, try to bite. For developers who must use npm (for example, working on a JavaScript-heavy codebase on a FreeBSD workstation), three layers are available without leaving the base system: Jails isolate npm install processes from the host filesystem and from other jails. A development jail per project caps the blast radius of any one compromised install. A simple base recipe: pkg install jq node npm service jail enable # /etc/jail.conf entry for a per-project jail Capsicum provides capability-mode sandboxing at the syscall level, available to processes that opt in. Wrapping a high-risk install in a capsicum-restricted shell is a meaningful additional layer. VuXML is FreeBSD's per-port vulnerability database, queryable via pkg audit. Ports affected by upstream advisories surface immediately: pkg audit -F None of these replace the structural problem npm itself presents. They reduce the blast radius of the consequences when (not if) the next wave lands. npm was designed for trust by default. Anyone can publish a package. Anyone who installs that package runs whatever code the author wrote. Anyone who depends on that package, transitively or directly, inherits that risk. The registry signs nothing the user must check. Provenance attestation is an opt-in feature that, in the 11 May wave, was used by the attackers themselves to make poisoned packages look more trustworthy than the clean ones. Each of these decisions was individually defensible in 2010, when the ecosystem was small enough that maintainer reputation could carry the weight. Each was a deliberate trade-off in favour of distribution speed. The combined result is, in 2026, the largest unsigned code-execution surface ever assembled. For any environment that takes its own security seriously, npm is simply not fit for purpose. The architecture is the product. One phished maintainer, one over-permissioned token, one approved bot pull request: each is enough, the mistake travels at machine speed, and there is no brake in the pipeline. There never was one. This week's wave was a campaign. The fire underneath has been the weather for years, and the forecast does not change. CISA Cybersecurity Advisories: Supply Chain Compromise Impacts Axios npm (20 April 2026): cisa.gov/news-events/alerts/2026/04/20/supply-chain-compromise-impacts-axios-node-package-manager Microsoft Security Blog: Mitigating the Axios npm Supply Chain Compromise (1 April 2026): microsoft.com/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise Google Cloud Threat Intelligence (Mandiant): UNC1069 activity clusters Palo Alto Unit 42: npm Supply Chain Attack Monitoring (ongoing): unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks Wiz: Mini Shai-Hulud TanStack Compromise (12 May 2026): wiz.io/blog/mini-shai-hulud-strikes-again-tanstack-more-npm-packages-compromised Snyk: TanStack npm Compromise Postmortem: snyk.io/blog/tanstack-npm-packages-compromised StepSecurity: Mini Shai-Hulud Self-Spreading Supply Chain Attack (12 May 2026): stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem Aikido: TanStack Compromise Technical Breakdown: aikido.dev/blog/mini-shai-hulud-is-back-tanstack-compromised vx-underground: Shai-Hulud source-code disclosure (12 May 2026) Sonatype State of the Software Supply Chain Report 2025 (454,648 malicious npm packages, >99% of OSS malware on npm) FreeBSD VuXML: vuxml.freebsd.org/freebsd/index.xml By Vivian Voss, System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.
