vulnerability scanner for the shai-hulud worm, single sh script, deep fast scanning with ripgrep on linux and mac
make it executable, you dont need root ( you can for full system scan ) it will scan whatever folder it is on, you can put it on your ~home folder or your project
chmod +x shaiscan.sh
./shaiscan.sh
for the healp file just run ./shaiscan.sh -h
shaiscan.sh v2.3.0-2026-05-12 — Mini Shai-Hulud (CVE-2026-45321) detector
USAGE
shaiscan.sh [OPTIONS]
OPTIONS
-r DIR Add DIR to scan roots (repeatable). Default: $PWD (current dir).
-A All-system preset: $HOME + (under root: /home/* or /Users/* + /root)
+ /usr/local /opt /etc, plus macOS LaunchAgents/LaunchDaemons.
-x DIR Exclude DIR (repeatable). Hard excludes (/proc /sys /dev /run /var/run
/var/lock) cannot be re-added. The env var $SHAISCAN_EXCLUDE may
carry a colon-separated list of additional paths to auto-exclude
(handy for slow NFS/NBD/SMB mounts).
-o FILE Write markdown report to FILE. Default: ./shaiscan-YYYY-MM-DD.md
-q Quiet progress output (report still written).
-j Also emit JSON summary at <report>.json
-V Print version and exit.
-h Print this help and exit.
Read-only, passive detector for Mini Shai-Hulud — the npm/PyPI supply-chain worm disclosed 2026-05-11 (CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx).
One POSIX shell script. No dependencies beyond awk, find, grep. Works on
Linux, macOS, and any system with a POSIX shell. Never modifies anything,
never contacts the worm's C2, never escalates privileges.
Quickstart — block the C2 in your hosts file (do this first, takes 30 seconds)
Even if you never run the scanner, just blackholing the worm's command-and-
control domains in /etc/hosts neutralises it on the machine you do this on.
The payload can still drop, but every connection attempt to its C2 fails
instantly. The exfil never lands. Your tokens never leave.
Copy-paste the block for your OS. The lines all start with 0.0.0.0 (IPv4
black hole) and :: (IPv6 black hole) — no real address, no traffic.
Linux
Open the file:
sudo nano /etc/hosts
Paste these lines at the bottom, save (Ctrl+O, Enter), exit (Ctrl+X):
0.0.0.0 git-tanstack.com
0.0.0.0 seed1.getsession.org
0.0.0.0 seed2.getsession.org
0.0.0.0 seed3.getsession.org
0.0.0.0 filev2.getsession.org
0.0.0.0 api.masscan.cloud
:: git-tanstack.com
:: seed1.getsession.org
:: seed2.getsession.org
:: seed3.getsession.org
:: filev2.getsession.org
:: api.masscan.cloud
Done. No restart needed.
macOS
Open the file:
sudo nano /etc/hosts
Paste the same block as Linux above. Save, exit. Then flush the DNS cache:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
Windows
Press the Start button, type
notepad, right-click Notepad, choose Run as administrator.In Notepad: File → Open, paste this path into the file-name box and press Enter:
C:\Windows\System32\drivers\etc\hosts(If you see no file, change the file-type dropdown from "Text Documents" to "All Files".)
Scroll to the bottom. Paste the same block as Linux above.
File → Save.
Done. No restart needed.
Verify it worked
On any OS, this should now print 0.0.0.0 (or fail to resolve):
ping git-tanstack.com
If it shows the real IP, the file did not save — re-open as admin / with
sudo and try again.
Why these six domains?
They are the worm's confirmed command-and-control endpoints. Sources: GHSA- g7cv-rxg3-hmpx and the upstream research published 2026-05-11.
| Domain | Role |
|---|---|
git-tanstack.com |
Payload host (typosquat of tanstack.com) |
seed1.getsession.org |
Covert C2 (abuses Session messenger network) |
seed2.getsession.org |
Covert C2 (same) |
seed3.getsession.org |
Covert C2 (same) |
filev2.getsession.org |
Exfil endpoint |
api.masscan.cloud |
Token-validity watcher |
Note: the seed*.getsession.org and filev2.getsession.org domains also
belong to the legitimate Session messenger project. The worm just abuses
that network as a covert channel. If you use Session messenger on this
machine, do not add these entries — pick a different defense (network
egress filter, separate VLAN).
Why 0.0.0.0 and not 127.0.0.1?
Both work. 0.0.0.0 is better:
- The connect call fails instantly with
EADDRNOTAVAILinstead of hitting your loopback interface. - It does not pollute logs of any local web server / dev server you happen to be running.
Run the scanner
After you've blackholed the C2 (above), run the scanner to check for existing infection.
./shaiscan.sh
That scans the current directory. The report is written to
./shaiscan-YYYY-MM-DD.md.
To scan the whole user account:
./shaiscan.sh -A
To scan the whole system as root (catches root-owned persistence):
sudo ./shaiscan.sh -A
The script needs no sudo to be useful — it just sees less under directories that are root-only readable.
What it checks
- Running processes for worm names (
gh-token-monitor,__DAEMONIZED,transformers.pyz,ctf-scramble*). - Persistence drops in
/tmp,~/.config,~/.claude,~/.vscode,~/Library/LaunchAgents,~/.config/systemd/user, crontabs,~/.npmrc, shell histories. - Network state: active sockets to the C2 IP,
/etc/hostsentries (blackhole =[OK], real-IP override =[CRIT]), DNS resolution probe (informational only). - Editor / CI hook tampering:
.claude/settings.jsonSessionStart hooks,.vscode/tasks.jsonfolderOpen,.github/workflows/*.ymlVARIABLE_STORE exfil pattern. - Lockfiles (
package-lock.json,npm-shrinkwrap.json,bun.lock,bun.lockb,yarn.lock,pnpm-lock.yaml) andpackage.jsonagainst the embedded IOC table (~110 packages, exact pinned versions). - Git-URL audit for the malicious commit hash that pinned a payload host.
- Installed
node_modulesversions. - Package-manager caches (
bun,npm,yarn,pnpm). - Python site-packages, pipx venvs, and Python lockfiles
(
Pipfile.lock,poetry.lock,uv.lock,requirements*.txt). - Registry-config sanity (
.npmrc,.bunfig.toml,.yarnrc{,.yml}). - SSH
authorized_keysand attack-window file mtimes (informational). - Filesystem IOC string scan (ripgrep fast path,
find+grepfallback).
Severity tiers
| Tag | Meaning |
|---|---|
[CRIT] |
Confirmed compromise. Act now. |
[REVIEW] |
Suspicious but ambiguous (e.g. a worm IOC string in a file that might be a security blog post you saved). Open the file and read context. |
[CACHED] |
A bad tarball lives in a package-manager cache. Cache files do not run on their own; clean the cache. |
[OK] |
A defensive measure was already in place on this host (e.g. /etc/hosts blackhole). Keep it. |
[i] |
Informational context. Not a finding. |
Exit codes
0— noCRITand noREVIEW(clean, orCACHED-only).1—CRITorREVIEWpresent (read the report).2— usage error.
CACHED does not fail the run — a tarball lingering on disk is not an
infection on its own.
Run your package manager's audit too
shaiscan only knows the IOCs embedded in its own table. Your package
manager pulls the live advisory feed and may flag advisories shaiscan does
not have yet. Run both:
bun audit # bun >= 1.1
npm audit
pnpm audit
yarn npm audit # yarn berry
What the scanner will NOT do
- It will not contact the C2, ever. No
curl, nowget, no DNS query outside what your local resolver does on its own. - It will not modify any file on the system.
- It will not delete a worm artifact for you. Image the disk before you start cleaning — the dropped files have forensic value.
- It will not rotate any credentials. Read the dead-man-switch warning printed at the top of every run before you rotate anything.
If the scanner finds something
The scanner prints a red banner before any output explaining the safe order of operations:
- Disconnect the network first.
- Image the disk if forensics matter.
- Kill the worm processes.
- Remove the persistence hooks.
- Then rotate every credential — from a different, clean PC.
The worm includes the bluff string
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner in stolen npm tokens
to discourage rotation, and installs a watcher (api.masscan.cloud) that
polls token validity and reacts on revocation. Do not revoke from a
network-connected, possibly-infected machine.
Authorised use
Scan systems you own or have explicit written permission to assess. Do not run against someone else's infrastructure.
Authority
IOCs sourced verbatim from the public GHSA-g7cv-rxg3-hmpx advisory and the upstream research disclosed 2026-05-11. No proprietary intel.
Comments