Shai-Hulud: Why a Secrets Manager Can't Save You From a Poisoned Dependency

Twice in 2025, a self-replicating npm worm stole credentials from thousands of machines before a single line of the victims' own code ran. It exposes the one assumption almost every secrets strategy is built on, and why that assumption is wrong.

The short version: the Shai-Hulud worm harvested API keys, cloud credentials, and tokens at install time, straight out of the environment variables and files where your secrets manager had just delivered them. That is the uncomfortable lesson: a secrets manager's job ends the instant it hands plaintext to your process, and a poisoned dependency runs inside that same process. The only way to survive this class of attack is to make sure the real key is never in the process at all.

On September 15, 2025, an engineer noticed something wrong with @ctrl/tinycolor, a color-parsing package with around two million weekly downloads. Within hours it was clear this was not an ordinary compromised package. It was a worm: malware that, once installed, used its victim's own credentials to infect the victim's other packages and publish them to npm automatically. No human attacker in the loop. It spread by itself.

Researchers named it Shai-Hulud, after the sandworms of Dune. The first wave compromised more than 500 packages, including modules published under CrowdStrike's npm scope. Then, in November 2025, it came back far larger. The second wave, whose payload tagged its exfiltration repositories "Sha1-Hulud: The Second Coming," exposed tens of thousands of GitHub repositories and hit packages from AsyncAPI, Zapier, PostHog, and Postman. It is one of the most consequential software supply chain attacks to date, and the mechanism is worth understanding precisely, because it invalidates a defense most teams believe they have.

What the worm actually did

The genius and the horror of Shai-Hulud is that it never needed to trick a human into running anything. It rode the npm install lifecycle itself.

1 · Install

You (or your CI pipeline) ran npm install with a compromised package somewhere in the dependency tree, often several layers deep in a transitive dependency you had never heard of. An install lifecycle script executed automatically, before any of your application code ran.

2 · Harvest

The script scanned the machine for credentials: environment variables, config files, npm tokens, GitHub personal access tokens, and AWS and Google Cloud keys. Where the OS allowed it, it ran TruffleHog, an off-the-shelf secret scanner, to find anything it had missed.

3 · Exfiltrate

Stolen secrets were serialized and shipped out. The first wave wrote a malicious GitHub Actions workflow that posted secrets to a webhook.site endpoint; the second wave pushed them to newly created public GitHub repositories for anyone to read.

4 · Replicate

Using a stolen npm token, the worm authenticated as the compromised maintainer, found their other packages, injected itself, and published new poisoned versions. Each new victim became a new spreader. That is what made it a worm rather than a one-off breach.

Sit with step two for a moment, because it is the whole point. The worm did not break your encryption. It did not crack your vault. It simply read the environment variables and files of the process it was running in, and your secrets were right there, in plaintext, because that is where every secrets tool puts them so your code can use them.

500+
npm packages compromised in the September 2025 first wave, including CrowdStrike-scoped modules.
25,000+
GitHub repositories exposed in the November 2025 "Second Coming" wave.
59%
of machines compromised were CI runners, per GitGuardian, the systems that hold your most powerful keys.

Why your secrets manager didn't stop it

This is the part that stings, so let's be exact about it. A secrets manager, whether that's HashiCorp Vault, Doppler, AWS Secrets Manager, or any of the others, does several genuinely valuable things: it centralizes secrets, encrypts them at rest, gates access with policy, gives you an audit trail, and lets you rotate on a schedule. If you don't have one, get one.

But every one of those tools works on the same model: you authenticate, it hands you the plaintext secret, and your process uses it. Whether the delivery mechanism is doppler run injecting environment variables, an AWS SDK call to GetSecretValue, or a mounted Kubernetes Secret, the end state is identical. The plaintext credential is sitting in the memory, environment, or filesystem of your running process.

Shai-Hulud ran in that exact process. A malicious dependency executes with the full permissions of the build or application that imported it. Everything your process can read, the worm can read. The secrets manager did its job perfectly and delivered the key to your workload, and that delivery is precisely what the worm harvested. The lock on the vault is irrelevant when the attacker is standing inside the room the vault opens into.

The numbers bear this out. In its State of Secrets Sprawl 2025 report, GitGuardian counted 28.6 million new leaked secrets, up 34% year over year, and found that even organizations with a dedicated secrets manager still leaked at around a 5.1% rate. A secrets manager reduces sprawl; it does not close the loop, because the loop ends at the moment of plaintext delivery.

The install-time problem is getting worse, not better

Two trends make this the defining credential-security problem of the next few years.

First, CI runners are the richest target and the softest one. They hold deploy keys, cloud admin credentials, registry tokens, and signing keys, and they run npm install against a dependency graph nobody has fully audited. When 59% of compromised machines are CI runners, the attackers have found the shortest path to the most valuable secrets in your organization.

Second, AI coding agents have made the process boundary even leakier. An autonomous agent that can install packages, run builds, and read files is a new, permissive execution context sitting on top of your credentials. Prompt-injected agents have already been shown dumping ANTHROPIC_API_KEY and GITHUB_TOKEN straight out of their environment. The environment variable that a worm reads at install time is the same environment variable an agent can be talked into printing.

The common thread is that the number of things running inside your process, with access to your process's secrets, is going up. Dependencies, transitive dependencies, build tooling, and now agents. Defending each one individually is a losing game.

The only durable fix: the key never enters the process

If the problem is that the plaintext key is present in a process full of untrusted code, the solution is to remove the plaintext key from the process. Not encrypt it better. Not rotate it faster. Remove it.

Concretely, that means putting an egress proxy between your application and the third-party APIs it calls. Your code makes its request to Stripe, OpenAI, Twilio, or SendGrid through the proxy, and the real vendor credential is injected at the last hop, on the way out, after it has left your infrastructure. Your application, its dependencies, its CI runners, and its environment variables only ever hold a short-lived, scoped, revocable token that authenticates to the proxy. The vendor key itself is never present in the process at all.

Run the Shai-Hulud playbook against that architecture and it comes up empty. The worm scans the environment and finds a KnoxCall token instead of a Stripe key. That token is scoped to specific routes, expires quickly, and can be revoked the instant anything looks wrong, without touching, or even knowing, the underlying vendor credential. The blast radius of a poisoned dependency collapses from "every key this process can see" to "one revocable token that only works through our proxy."

This is the design principle behind KnoxCall. You route your outbound API calls through KnoxCall, and it injects the real vendor key at the egress wire. Your workload never holds it, so a poisoned dependency, a compromised CI runner, or a prompt-injected agent has nothing to steal. It runs alongside your existing secrets manager rather than replacing it: keep Vault or Doppler for your database passwords and internal secrets, and let KnoxCall keep the third-party vendor keys out of the process entirely.

What to do now, in order

  • Rotate anything a build touched. If any credential was ever readable by a CI pipeline or a developer machine during the affected windows, treat it as exposed and rotate it. Start with npm tokens, GitHub PATs, and cloud keys.
  • Pin and lock dependencies, and disable install scripts where you can. Use lockfiles, review dependency updates, and run npm install --ignore-scripts in CI where your build allows it. This shrinks the install-time attack surface but does not eliminate it.
  • Get every plaintext third-party key out of your environment. This is the structural fix. For the vendor APIs you call, move to egress injection so the real key never enters a process again. Everything above is damage control; this is the part that makes the next worm a non-event.

Shai-Hulud will not be the last self-replicating supply chain worm. The registries are too central and the install-time execution model too permissive for this to be a one-time event. The teams that come through the next one unscathed will be the ones whose secrets were never in the room when the attacker walked in.

Frequently asked questions

What was the Shai-Hulud npm worm?

Shai-Hulud was a self-replicating worm that spread through the npm registry. It first hit in September 2025, compromising over 500 packages including @ctrl/tinycolor and CrowdStrike-scoped packages, and returned in a much larger November 2025 wave ("The Second Coming") that exposed tens of thousands of GitHub repositories. When an infected package was installed, a script ran automatically and harvested credentials from the machine, then used any stolen npm token to inject itself into the victim's own packages and publish them, spreading exponentially without any human attacker.

Does a secrets manager protect against supply chain attacks like Shai-Hulud?

Only partially. A secrets manager centralizes, encrypts, and audits your secrets, but its job ends the moment it hands the plaintext value to your running process. Shai-Hulud harvested credentials from environment variables and files on disk at install time, which is exactly where a secrets manager delivers them. If the key is readable by your process, it is readable by a malicious dependency running in that same process. GitGuardian found that even organizations with a dedicated secrets manager still leaked secrets at around a 5.1% rate.

How do you actually stop a poisoned dependency from stealing API keys?

The only durable fix is to make sure the plaintext key is never present in the process at all. If your third-party API keys live behind an egress proxy that injects the real credential at the last hop, your application, its dependencies, its CI runners, and its environment variables only ever hold a short-lived, scoped, revocable token, never the vendor key itself. A worm scanning that process finds nothing worth stealing, and any token it does find can be revoked instantly without rotating the underlying key.

Why are install-time attacks so dangerous?

Install-time attacks run before any of your own code executes and before most runtime defenses are active. An npm lifecycle script (like postinstall) fires during npm install, which happens constantly in local development and, more dangerously, on CI runners that typically hold the most powerful credentials in an organization. GitGuardian found 59% of the machines compromised by Shai-Hulud were CI runners. Because the malicious code runs with the same permissions as your build, it can read every environment variable and file the build can, which is usually everything.

Keep your secrets manager. Take the vendor keys out of the process anyway.

KnoxCall injects your third-party API keys at the egress wire, so a poisoned dependency, a compromised CI runner, or a prompt-injected agent never has a key to steal.

Start Free