As of May 12, 2026, the npm supply-chain incident developers need to watch is the Mini Shai-Hulud wave that compromised TanStack packages and many other npm artifacts.
The attack is especially dangerous because it not only steals credentials, but it also uses them to publish more malicious packages automatically.
How the malware works
Mini Shai-Hulud is dangerous because it combines two familiar ideas: code that runs during install, and secret theft.
The simplified flow looks like this:
- A malicious package version lands in the registry.
- A developer machine or CI runner installs it.
- npm runs lifecycle scripts unless the environment blocks them.
- The malicious script scans the environment for secrets.
- If it finds publishing credentials, GitHub tokens, cloud credentials, or package manager tokens, it tries to steal them and use them to continue the campaign.
- It drops repo files for Claude Code, Codex, and VS Code that run automatically when the folder is opened.
- The next time a developer opens that repo in Claude Code, Codex, or VS Code, the hook runs the malware again.
- The malware gets another chance to steal fresh tokens and spread to more packages.
This malware also installs a dead-man's switch. If it loses access to the stolen GitHub credentials or cannot keep its exfiltration path alive, it may try to delete the user's home directory. On Windows that means %USERPROFILE%; on Unix systems that means ~.
Snyk reported that the campaign affected 84 npm package artifacts across 42 TanStack package names, with related packages also affected. So far, Mini Shai-Hulud spans well over 160 package versions across npm and PyPI.
The technical lesson is the same one we saw in the earlier axios compromise from March 31, 2026. In that case, malicious versions pulled in a hidden dependency and ran a malicious postinstall script. The important point is that package installs can run code on your machine, so publishing credentials need the same level of protection as production secrets.
How It Started
The TanStack compromise started in GitHub.
A pull request hit a workflow that used the pull_request_target event. That workflow had access to the trusted repo context, but it was handling untrusted code from a fork. The attacker used that gap to poison GitHub Actions cache and get code running in the wrong place at the wrong time.
Once that happened, the runner exposed a token that could be used to publish packages. From there, the attacker pushed malicious npm versions to the registry.
What developers should do
1. Disable lifecycle scripts by default
Post-install scripts are one of the most common paths for supply-chain malware. The npm security best practices guide recommends disabling them globally:
1npm config set ignore-scripts trueIf you need an ad-hoc install, keep the scope narrow:
1npm install --ignore-scripts <package-name>If you are on npm CLI 11.10.0+, also set allow-git=none. Git-based dependencies can bring their own .npmrc, and that can re-enable lifecycle scripts even when --ignore-scripts is set.
1npm config set ignore-scripts true
2npm config set allow-git noneOn older npm versions, skip allow-git and rely on --ignore-scripts plus npm ci in CI.
2. Install with cooldown
The most useful defense against fast-moving npm attacks is a release-age gate. If a malicious version is live for only hours or one day, a cooldown period can keep it out of your installs while maintainers, scanners, and the community react.
If you are on npm CLI 11.10.0+, we recommend:
1npm config set min-release-age 3That delay means npm will not automatically install versions published less than 3 days ago. For many teams, 3 days is enough to avoid releases that are compromised and then quickly removed. Higher-risk environments may want a longer window. On older npm versions, this setting is not available.
3. Use deterministic installs in CI
Do not use plain npm install in production pipelines. Use npm ci so the install follows the lockfile exactly:
1npm ciThis does not stop a bad version that is already in the lockfile. It does stop unplanned version drift, which makes it harder for a surprise publish to slip in through a loose semver range.
4. Put a security gate in front of installs
Tools like npq and Socket Firewall can inspect a package before it lands on disk. That gives you another line of defense against freshly published or suspicious packages.
Examples:
1npq install express
2sfw npm install expressThese tools are not a full security policy, but they are useful tripwires.
5. Stop using npx as a blind launcher
npx resolves, downloads, and runs packages in one step. That is convenient, but it can also pull in a newly compromised version without review. If you need a tool, install it in a workspace with a lockfile and run the reviewed binary from there.
6. Pin and review dependency changes
Lockfiles are only useful if you review what changed. When a dependency update is part of a PR:
- Check whether it is a new package or a version bump.
- Look for newly added lifecycle scripts.
- Look for unexplained dependency additions.
- Treat version bumps as security-sensitive when the package is widely used.
What package maintainers should do
Developers can harden installs, but maintainers also need to reduce the chance that their account or CI pipeline becomes the delivery path.
1. Turn on 2FA with write access
If a maintainer account can publish packages, it should have 2FA enabled:
1npm profile enable-2fa auth-and-writesThat does not make account takeover impossible, but it makes the attack harder.
2. Publish with provenance and OIDC
Long-lived npm tokens are risky because they can be stolen and reused. The current best practice is trusted publishing through OIDC, ideally with provenance attestation:
1permissions:
2 id-token: write1npm publish --provenanceThis moves publishing away from static secrets and toward short-lived credentials tied to your CI workflow. It also makes releases easier to audit.
3. Reduce the dependency tree
Every extra dependency is another package that can be compromised, abandoned, or hijacked. Risk goes down when the package tree is smaller, the release process is repeatable, and the list of people who can publish is tightly controlled.
4. Watch for account takeover signals
Maintainers of high-traffic packages should treat login alerts, unexpected publish attempts, and new device logins as real security events. The TanStack postmortem makes it clear that the attacker did not need to break npm itself. They only needed one trusted publishing path.
A practical baseline
If you want a safer default for most teams, start here:
1npm config set ignore-scripts true
2npm config set min-release-age 3Then in CI:
1npm ciAnd for maintainers:
1npm profile enable-2fa auth-and-writes
2npm publish --provenanceThat combination will not remove supply-chain risk, but it blocks the easiest paths: surprise lifecycle execution, immediate adoption of brand-new releases, and weak publishing credentials.
Sources and further reading
- Postmortem: TanStack npm supply-chain compromise
- TanStack npm Packages Hit by Mini Shai-Hulud - Snyk
- TeamPCP's Mini Shai-Hulud Is Back - StepSecurity
- Shai-Hulud: Here We Go Again - JFrog Security Research
- npm Security Best Practices by Liran Tal
FAQ
How do I know if my project was affected?
Check whether you installed or built from any affected package versions during the incident window. Start with your lockfile, CI logs, and package manager cache. If you see a suspicious version, rotate any secrets that may have been present on that machine or runner.
You can also check for the dead-man's-switch persistence with this command:
1# macOS
2launchctl list | grep -E 'gh-token-monitor|com\.user\.gh-token-monitor'
3launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null
4rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
5
6# Linux
7systemctl --user status gh-token-monitor --no-pager
8systemctl --user stop gh-token-monitor 2>/dev/null
9systemctl --user disable gh-token-monitor 2>/dev/null
10rm -f ~/.config/*gh-token-monitor* ~/.local/bin/gh-token-monitor.shIf any of those commands show a match, treat the machine as affected and remove the matching files or services before revoking tokens.
Should I check Claude Code, Codex, or VS Code files too?
Yes. This campaign can drop repo files that run again when you open the project in Claude Code, Codex, or VS Code. Check for .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, .vscode/setup.mjs, and any Codex config or hook files in the repo. If they are present and you did not add them, treat the repo as compromised.
Is npm install always unsafe?
No. It is normal to use npm install. The risk is that it can run package scripts and can pull in new versions that you have not reviewed yet. That is why ignore-scripts, cooldowns, and lockfile-based installs matter.
Why is min-release-age useful?
It slows down adoption of very new releases. That gives the community time to spot and remove bad packages before they spread widely.
What if allow-git is not supported on my npm version?
Skip it. Use npm config set ignore-scripts true and npm ci in CI. If you later upgrade to npm CLI 11.10.0+, you can add allow-git=none as an extra guard.
What should maintainers do first?
Turn on 2FA, move to trusted publishing with OIDC, and reduce the number of people and systems that can publish a release. Those three changes make account takeover much less useful to an attacker.
