OrbitalReg Sign in →

Supply-chain case study

Sept & Nov 2025 · npm

How OrbitalReg's verify-on-pull would have stopped Shai-Hulud at your registry edge.

Shai-Hulud is the self-replicating npm worm that, between September and December 2025, compromised 796 npm packages summing over 20 million weekly downloads, and seeded 25,000+ malicious GitHub repositories using stolen developer credentials. This page walks through the attack chain and shows precisely where an OrbitalReg-mediated registry breaks it.

Attack chain

The attack in 90 seconds.

  1. 01. Maintainer phishing. An attacker sends a maintainer of a popular package a fake npm security alert, capturing the login and 2FA code.
  2. 02. Poisoned publish. The attacker publishes a patch-version release of the package with a malicious preinstall script — a single bundle.js that runs before any test, lint, or signature check.
  3. 03. Credential exfiltration. On every developer machine that runs npm install, the payload harvests environment secrets (NPM tokens, GitHub PATs, AWS / GCP / Azure credentials) and exfiltrates them to public attacker-owned GitHub repos named Shai-Hulud: The Second Coming.
  4. 04. Self-replication. The payload uses the stolen NPM token to publish poisoned versions of every other package that maintainer owns — and uses the stolen GitHub PAT to mass-clone repos and seed a malicious workflow file. No command-and-control server required.
The critical property: the payload runs on the consumer's machine, at preinstall time — before npm audit, before tests, before any signature verification the consumer's CI might do. By the time CI fails, the developer's laptop has already shipped the secrets.

The structural gap

Why direct npmjs.org pulls had no gate.

When a developer machine — laptop, CI runner, or build container — does npm install directly against registry.npmjs.org, there is nothing between the package and the preinstall script execution. The tarball lands, npm parses package.json, the script runs as the invoking user. No allow-list, no signature check (npm doesn't broadly Sigstore-sign), no CVE gate.

Every commonly-recommended mitigation — npm audit, Dependabot, Snyk CI scans — runs after the install. That is the right model for catching code-injection bugs in your own repo, but the wrong model for a worm whose entire purpose is to execute during install.

The only structural fix is to route every pull through a registry that you control, where the gate runs before the tarball is handed to the developer machine.

The OrbitalReg defense pattern

Three layers between the attacker and your developer's machine.

01

Scan-on-proxy

Every npm tarball pulled through OrbitalReg lands in object storage and is fed to Trivy + Grype + OSV-scanner. The OSV database receives Shai-Hulud GHSA advisories within hours of disclosure. Once a poisoned version is in the advisory feed, OrbitalReg's next scan flags it as critical-severity.

02

Verify-on-pull

Per-repo policy: refuse to serve any artifact carrying a critical CVE. A developer running npm install against your OrbitalReg npm repo receives a 403 with the GHSA ID in the response body — the tarball never reaches their machine, the preinstall script never runs.

03

Air-gap promotion

For the highest-sensitivity tier: configure your prod-npm repo as local-only and route uploads through a staging proxy with a manual promotion step. A poisoned tarball lands in staging, the scanner marks it critical, the promotion request fails the policy gate. The bad version never reaches the prod repo your developers actually pull from.

What this composes to. Once the GHSA advisory for a poisoned version is in the OSV feed (hours, not days, for both Shai-Hulud waves), the registry edge becomes the chokepoint: every developer machine, every CI runner, every build container that pulls through your OrbitalReg gets the 403 instead of the payload. No fleet-wide credential rotation, no incident war room, no public disclosure to your customers about exposed secrets.

Six lines

The actual config.

This is the minimum to turn on layer 1 + layer 2 for an npm repo. Drop into your IaC or run it directly with the orbital CLI.

# Create a verify-on-pull-gated remote npm repo.
# Scanner: trivy + osv. Block any artifact with a critical CVE.

orbital repo create npm-prod \
  --format=npm --kind=remote \
  --upstream=https://registry.npmjs.org \
  --scanner=trivy,osv \
  --pull-gate=block-on-cve-critical

Full pull-gate grammar — including custom thresholds, license-policy gates, and signature-verification policies — is documented at docs.orbitalreg.com/guide/pull-gates.

For your records

Shai-Hulud timeline.

2025-09-15
Initial wave. Malicious versions of multiple popular packages appear on npm carrying a postinstall payload that exfiltrates secrets to attacker-created GitHub repos named Shai-Hulud. CISA and Unit42 publish IoC lists within 48 h.
2025-09-23
CISA Cybersecurity Advisory issued. GHSA advisories filed for each affected version; OSV feed updated. From this point any scanner with a current advisory DB recognises the poisoned versions as critical.
2025-11-24
Shai-Hulud 2.0 (The Second Coming) begins. Significantly wider in scope — 796 unique npm packages eventually backdoored, 25,000+ malicious GitHub repos seeded across ~350 user accounts. Confirmed maintainer-account compromises at Zapier, PostHog, Postman, and others. Cumulative weekly downloads of affected packages exceed 20 million.
2025-12-09
Microsoft Security Response publishes detection / investigation guidance covering the 2.0 wave. By this point all known poisoned versions are in advisory feeds — they are blocked automatically by any registry running verify-on-pull on critical CVEs.

Honest caveats

What this does not claim.

It is not zero-day protection. If the malicious version is in your registry cache before the GHSA advisory exists, OrbitalReg will serve it. The window between the poisoned publish and the advisory landing is typically a few hours — small, but non-zero. For both Shai-Hulud waves, that window cost some users their secrets. We're honest about that.

It does not replace Sigstore. OrbitalReg checks Sigstore signatures when upstream signs. The npm ecosystem currently signs only a sub-fraction of published packages, so signature verification alone catches a minority of supply-chain attacks. CVE-based gates fill the gap until publish-time signing becomes universal.

It needs a fresh scanner DB. OSV / Trivy / Grype databases update on a schedule. If your OrbitalReg deployment hasn't pulled the latest advisory feed, the gate works with stale knowledge. The default cadence is hourly; mission-critical deployments can run it on a sub-15-minute timer.

It does not stop social engineering. OrbitalReg can't keep a maintainer from being phished. It constrains the blast radius after the fact — limiting the damage to the maintainer's own machine, before the worm has a chance to reach anyone else's.

Primary sources

Want this configuration for your team?

Talk to us about hardening your npm supply chain.

Rico, the founder, walks you through the deployment, the policy grammar, the scanner DB cadence — and the bits where we honestly can't help, so you can make an informed call.