DEV Community

Pico
Pico

Posted on • Originally published at getcommit.dev

The npm Worm Has Been Public for 31 Days. Two Derivatives Have Shipped.

TeamPCP pushed Mini Shai-Hulud to GitHub on May 12. Within a month, two independent campaigns had forked it. Each invented a new install-time bypass the previous defense couldn't survive. The pattern is now clear enough to predict the next one.

On May 11, 2026, TeamPCP poisoned 172 packages across npm and PyPI in a single coordinated wave. TanStack, Mistral AI, OpenSearch, UiPath, Guardrails AI. The mechanism wasn't a typo-squat or a leaked credential. It was a misconfigured GitHub Actions workflow on TanStack itself. The malware exchanged the ambient OIDC token for npm publish access and shipped legitimately-attested malicious tarballs to the registry.

Twenty-four hours later, TeamPCP published the source. Akamai dated the release to the evening of May 12. The worm became a starter kit.

The original was uncatchable by scoring

I want to be honest about this. The packages TeamPCP hit on May 11 were high-trust. @tanstack/react-router has 19.7M weekly downloads, 5 publishers, MIT license, active development. It scores 91 on Commit's behavioral audit. The attack didn't compromise the publishers. It compromised the pipeline those publishers used to release.

A scoring layer that measures publisher concentration, maintainer continuity, and release cadence will not flag @tanstack/react-router. It shouldn't. Those signals are doing their job. They describe a healthy package. The TanStack attack walked around them by skipping the publisher layer entirely.

If the only campaigns we ever had to defend against were CI/CD pipeline hijacks against top-50 npm packages, behavioral scoring would be irrelevant. That's not what came next.

Derivative #1: Red Hat Miasma (June 1)

Twenty days after the source release, JFrog reported that 96 versions across 32 @redhat-cloud-services packages had been republished with malicious preinstall hooks. The packages carried valid SLSA Build Level 3 provenance, signed by Red Hat's own build environment after that environment had been quietly compromised.

The payload set "Miasma: The Spreading Blight" as the description on attacker-created exfiltration repos. That string is now the field marker for everything downstream from the open-sourced code. It is not a TeamPCP marker. It belongs to whoever ran the binary.

Red Hat's packages scored 65–83. That's high. Once again, the attack used compromised infrastructure on otherwise healthy packages. Provenance was meant to be the defense layer here. It signed the malware too.

Derivative #2: Phantom Gyp (June 3)

Forty-eight hours after Red Hat, the lifecycle-script defense closed. So the next wave stopped using lifecycle scripts. A 157-byte binding.gyp file triggers node-gyp rebuild during install — it's how native addons have built since 2012. StepSecurity named the technique "Phantom Gyp."

But here is the part nobody has emphasized: the packages Phantom Gyp hit are completely different from the TanStack wave. They are not high-trust at all.

Wave Example package Score Publishers Weekly DL
TanStack (May 11) @tanstack/react-router 91 5 19.7M
Red Hat Miasma (Jun 1) @redhat-cloud-services/types 65–83 multi 80K
Phantom Gyp (Jun 3) autotel 40 1 6K
Phantom Gyp (Jun 3) awaitly 32 1 287
Phantom Gyp (Jun 3) node-env-resolver 28 1 92

The score profile inverted. TeamPCP went after the top of the registry through the pipeline. The derivatives are working their way down the long tail through the publishers themselves. They harvest single-publisher accounts with no second pair of eyes, then republish whatever those accounts already own.

The pattern, plainly stated

Three waves of one malware family, in 23 days:

  • TanStack (May 11): high-trust packages, CI/CD pipeline as the attack surface, postinstall as the execution path.
  • Red Hat (Jun 1): medium-trust packages, build environment as the attack surface, preinstall + valid provenance as the execution path.
  • Phantom Gyp (Jun 3): low-trust packages, single-publisher account takeover as the attack surface, binding.gyp as the execution path — no lifecycle hook at all.

Each derivative bypasses the defense that flagged the last one. None of them recycles a technique. And the target profile shifts down the trust gradient with each iteration: from 91 to 65–83 to 28–49.

What this implies for the next wave

The TanStack-style attacks need a CI/CD posture defense: pinning Actions to commit SHAs, locking down OIDC scopes, scanning Actions for memory-scraping behavior on the runner. Behavioral scoring is the wrong layer to catch those, and I'm not going to pretend otherwise.

The Phantom Gyp side is exactly the shape behavioral scoring exists to catch. Single publisher. No release cadence. Limited adoption. No community oversight. The technique changes (today binding.gyp; next month something nobody has written about yet). The structural profile of the compromised account does not change. That's the constant the scoring layer is built around.

A reasonable bet on the next derivative: another no-lifecycle execution path (npm-shrinkwrap.json manipulation, files-field smuggling, something in the resolver), targeting another tier of single-publisher packages with the score profile in the 30s. If that's wrong, it's wrong cheap. The defense is the same either way.

What to do today

If you're running CI: pin every GitHub Action to a commit SHA, not a tag. Scope OIDC tokens to the minimum publishing surface. The TanStack pattern walks in through dependency installs in CI. Treat your runners as the same blast radius as your registry credentials.

If you're picking dependencies: the long-tail packages getting hit by derivative waves all score below 60. Run your lockfile through a behavioral audit and you'll see which ones sit at the same risk profile as awaitly, autotel, and node-env-resolver, before they ship the next variant.

npx proof-of-commitment
Enter fullscreen mode Exit fullscreen mode

Scores every dependency in your project against the same signals that already separated the TanStack victims (high) from the Phantom Gyp victims (low). The toolkit is public. The pattern is public. The defense for the long-tail half of it is one command.


Originally published at getcommit.dev. Commit scores npm, PyPI, Cargo, and Go packages on behavioral commitment — signals harder to fake than stars, READMEs, or download counts.

Top comments (0)