DEV Community

Vivian Voss
Vivian Voss

Posted on • Originally published at vivianvoss.net

npm Is on Fire: Why the Architecture Is the Product

A wide cinematic landscape at dusk, rendered in clean European-comic style. The ground stretches to the horizon as a vast sea of stacked wooden shipping crates piled into long dune-like ridges. From the middle of the dunes a massive sandworm has erupted: its enormous segmented body coils upward against the pink and orange dusk sky, mouth open in a wide circular maw lined with concentric rings of teeth, body segments dark and faintly iridescent. Splinters of broken crates spray around the worm's eruption point. On the left side of the frame, a young developer walks along a raised wooden walkway above the package field, three-quarter view from behind, pulling a small wooden handcart with two crates. She has paused mid-step, looking up at the worm with the calm recognition of someone who has seen this pattern escalate before. Palette: warm dusk pink and orange sky, cool blue-grey shadow on the package dunes, dark serpentine worm with subtle blue-green iridescence on its segments. The mood is the quiet recognition that the predator has emerged from the supply chain itself.

Wire Fire: Episode 01

The Permanent State

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.

Six Weeks of Wire

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.

31 March 2026: the axios compromise

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, plain-crypto-js@4.2.1, 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.

29 April 2026: Mini Shai-Hulud hits the SAP namespace

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.

11 May 2026: TanStack and the OIDC chain

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.

12 May 2026: the worm goes public

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.

Shai-Hulud, Generation Four

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.

The Mechanism in Plain Words

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:

  1. Steal credentials from every machine that runs the next install.
  2. Use those credentials to publish new poisoned versions under the stolen identity.
  3. Propagate to every project that depends on those packages.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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.

What This Means If You Are Using npm

The response depends on your role.

If you are a developer

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:

  1. 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.
  2. Pin transitive dependencies via a lockfile and review changes to that lockfile in pull requests as if they were code, because they are.
  3. 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.
  4. Treat any package with more than a hundred transitive maintainers as if you were accepting code from a hundred strangers, because you are.

If you are responsible for operations or security

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.

If you are deciding on a stack

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.

A FreeBSD Sidebar

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:

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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.

  2. VuXML is FreeBSD's per-port vulnerability database, queryable via pkg audit. Ports affected by upstream advisories surface immediately:

   pkg audit -F
Enter fullscreen mode Exit fullscreen mode

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.

The Architecture Is the Product

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.

Sources

  • 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.

Top comments (0)