What to do after you installed a compromised npm package
Removing the package is the least important step and most teams stop there. The credentials it already took are the incident. An ordered response, and the reasoning behind the order.
The instinct is to uninstall the package, upgrade past the bad version, and move on. That handles the part of the incident that already finished.
Modern npm malware is a credential harvester. By the time you read the advisory it has already run, already read your environment, and already sent what it found somewhere. The Shai-Hulud family makes this explicit: it scans CI/CD environments, environment variables, cloud metadata endpoints and the filesystem with TruffleHog, then exfiltrates by creating public GitHub repositories full of JSON dumps and posting the same data to attacker webhooks.
Removing the package does not revoke anything it took. Stolen tokens, rogue Actions runners and exfiltrated cloud credentials all survive the uninstall. That is the whole reason this list is ordered the way it is.
First: contain, before you investigate#
Investigation is slower than the attacker. Do these before you understand the full scope, and accept some unnecessary rotation as the cost.
1. Revoke npm tokens
Every automation token with write permission, first. These are what turn one compromised developer into a worm — the malware authenticates as you, enumerates your other packages, and publishes to them.
npm token list
npm token revoke <id>
Tokens with bypass_2fa are the priority. If you are not sure which those
are, revoke all of them and reissue.
2. Revoke GitHub credentials
Personal access tokens, session tokens, SSH keys and OAuth app grants on any machine that ran the install. GitHub tokens are what let the worm create the exfiltration repositories and write workflows into your repos.
3. Rotate everything that was in reach
Anything readable from the environment where the install ran: cloud provider keys, Kubernetes configs, Vault tokens, CI/CD secrets, database URLs, API keys for AI tooling. If it was in an environment variable or a dotfile on that machine, treat it as disclosed.
The scope question is not "what did this package need?" — it is "what could a process running as me read?"
Then: look for what it left behind#
4. Check for repositories you did not create
The exfiltration mechanism is a public repository under your own account. Look for
repositories named with Dune terminology — Shai-Hulud and variants — or with
descriptions containing strings like "Shai-Hulud: Here We Go Again". The AntV wave alone
created over 2,200 of these.
Also check whether any of your private repositories became public. The worm flips visibility to widen the blast radius, and that change is easy to miss because nothing about your local clone reflects it.
5. Check for workflows you did not write
git log --all --diff-filter=A -- '.github/workflows/'
The signature file is .github/workflows/shai-hulud-workflow.yml, but do not
grep for that name alone — the copycat waves since TeamPCP published the source in May
2026 use different filenames. Look for any workflow that touches secrets and posts to an
external host.
6. Check for commits you did not make
Across every repository the compromised credentials could reach, not just the one you were working in.
7. Audit CI runners
Self-hosted runners can be left persistently registered. A runner that survives your cleanup is a shell that survives your cleanup.
Last: the package itself#
8. Identify what actually resolved
Your package.json is not the record of what you installed. The lockfile
is.
npm ls <package-name>
grep -n "<package-name>" package-lock.json
In the August 2026 CHAINDROP wave the affected list was keyv,
flat-cache, file-entry-cache, cacheable-request,
cache-manager — over 400 packages in total. Almost nobody affected had any of
them in their package.json. They arrived as transitive dependencies of things
people did choose.
9. Pin past it and rebuild clean
Delete node_modules and the lockfile entries for the affected tree, then
reinstall with scripts disabled while you verify:
rm -rf node_modules
npm install --ignore-scripts
npm ls <package-name>
10. Rebuild the machine if it ran with privileges
For a developer laptop this is usually disproportionate. For a CI runner or a machine holding production credentials it is not — you cannot enumerate what a script did with your shell, and a clean image is cheaper than being wrong.
What to do tomorrow#
Three changes prevent most of the recurrence, and none of them is a scanner.
Disable install scripts by default. If you are on npm 12 or later,
preinstall hooks are already blocked by default. If not, make
npm ci --ignore-scripts the standard CI invocation. In one benchmark of 6,420
malicious packages, 72.21% used lifecycle scripts as the vector.
Introduce a soak period. Do not let builds adopt a version the day it is published. Every wave described here was identified within days. A 72-hour delay turns most of this class into something you read about instead of something you ran, and it costs you nothing that matters.
Put the check at the shell, not in a policy document. Nothing above would have helped at the moment of install, because nothing above runs then. If an agent or a script is doing the installing, the gate has to be where the install happens — that is what DepWall is.
One honest caveat, since this post is mostly about Shai-Hulud: a compromised maintainer publishing through their normal pipeline is the case our own signals are weakest on. Right name, real history, valid provenance. We keep it in the corpus as a fixture we do not claim to detect. The soak period is doing more work against this class than any scanner is, ours included.
Related#
How npm supply-chain attacks actually reach your machine — the five delivery paths and which controls map to which.