Shai-Hulud 2.0 compromised npm packages uploaded between 21 and 23 November 2025. The registry cleaned up within days. Nine months later it is still producing breaches, and the reason is that the packages were never the payload.

Nobody agrees how big it was

SourcePackages compromised
One analysis796 unique packages backdoored
Another"over 700"
Another600–800

Malicious GitHub repositories created: over 25,000 by one count, more than 27,000 by another.

We are reporting the range rather than picking a number, because the counts were taken at different moments during an actively spreading incident and none of the published figures states its cutoff. Anyone quoting a single precise figure for this campaign is quoting one vendor's snapshot.

Named organisations affected include Zapier, PostHog and Postman. The campaign also spread from npm into Maven.

How it worked

The malware executed during npm's preinstall phase — before any of your own code ran, and before most tooling was watching — via a script named setup_bun.js, which dropped a heavily obfuscated payload, bun_environment.js.

What it collected:

  • GitHub Personal Access Tokens
  • npm authentication tokens
  • Cloud credentials for AWS, GCP and Azure

And then it did the thing that makes this campaign different from every other registry compromise: it published the stolen secrets to a public GitHub repository under the victim's own account.

Not to attacker infrastructure. To a public repo, in the victim's name, indexed and readable by anyone.

Why that choice is the whole story

Exfiltrating to attacker-controlled infrastructure gives defenders something to work with — a domain to block, a server to seize, an IOC to distribute. Publishing to the victim's own GitHub gives them none of that.

It also converts a time-limited theft into a permanent public dataset. The worm's operator does not need to hold the credentials. Nobody needs to. They are simply available, to anyone who thought to look, for as long as they remain valid.

GitGuardian measured exactly this. Analysing 4,645 of the repositories, it found 11,858 unique secrets, of which 2,298 were still valid and publicly exposed as of 24 November 2025 — days into the incident, with the industry already responding.

That is roughly a 19% survival rate on secrets that everyone knew were public.

Nine months later

The clearest illustration is a breach nobody initially connected to this campaign.

The AI music company Suno was compromised in November 2025 when a single employee's machine was infected by Shai-Hulud and their GitHub and cloud credentials were harvested and auto-published. A person using the handle "ellie.191" used those credentials to reach Suno's repositories and customer records — and Socket's analysis is explicit that it cannot determine whether that person deployed the worm or simply found the credentials in a public repo.

The second possibility is the one that should worry you. It means the intrusion required no relationship with the malware campaign at all. Someone read a public repository.

We covered what that breach did and did not show: 55 million customer records, and a company that told nobody for eight months.

That is what an incomplete rotation costs. The same pattern produced the poisoned LiteLLM releases, where a publishing token survived the cleanup after a different compromise and was used a week later against an unrelated project.

The lesson people took, and the one they should have

The lesson taken from Shai-Hulud was about npm: pin versions, audit dependencies, disable install scripts. All correct, all worth doing.

The lesson available is about rotation.

A registry compromise is over when the packages come down. A credential compromise is over when every credential is rotated — and "every" includes the ones no human logs in with: publishing tokens, deploy keys, CI service accounts, machine identities. Those are the ones that get missed, because nothing breaks when they are left alone, and nothing alerts when they are used.

What to do, if you have not already

  • Assume any credential present on a developer machine or in CI between 21–23 November 2025 is public. Not "possibly compromised" — public.
  • Rotate machine identities, not just user ones. Publishing tokens, deploy keys, CI service accounts, webhook secrets.
  • Search GitHub for repositories under your own org's accounts that you did not create. The worm published under victim identities; the evidence may be sitting in your own namespace.
  • Disable npm lifecycle scripts by default (npm config set ignore-scripts true) and allow them per-package where genuinely needed. The payload ran at preinstall.
  • Treat "we rotated after that incident" as a claim to verify, not a fact to record. The LiteLLM case turned on a rotation everyone believed was complete.

The uncomfortable arithmetic: 2,298 secrets were still valid and public during the response, with maximum attention on the problem. Nobody has published a figure for how many are still valid now.