A single, dependency-light Bash script that scans your machine and every local Git repo for artifacts of the Shai-Hulud npm supply-chain worm (and its Miasma / mini-shai-hulud variants).
It is read-only — it greps and reports, never writes, deletes, or "fixes" anything — so it is safe to run repeatedly, including on a machine you suspect is already compromised.
Shai-Hulud is a self-replicating supply-chain worm that spread through the npm ecosystem (first wave September 2025; a second wave, "Sha1-Hulud: The Second Coming," in November 2025). Once a developer installs a trojanized package, the payload:
- harvests secrets from the machine — npm, GitHub, and cloud (AWS/GCP/Azure) credentials — often by downloading and running TruffleHog;
- exfiltrates the stolen data to attacker-controlled and public GitHub repositories;
- plants malicious GitHub Actions workflows (e.g.
.github/workflows/shai-hulud-workflow.yml) and dropper scripts (setup_bun.js,bun_environment.js,router_init.js, …); and - self-propagates by publishing trojanized versions of every package the compromised maintainer can push to.
Because the worm touches so many places — your shell history, persistence configs, npm caches, Git history, and the working trees of dozens of repos — checking by hand is error-prone. This script automates that sweep using indicators of compromise (IOCs) drawn from public research, and sorts every finding into one of three clearly-labeled severity tiers.
- Worm tooling —
bunandtrufflehogbinaries on yourPATH(the worm installs/uses these). - Worm filesystem paths —
~/.truffler-cache,~/.dev-env,~/actions-runner,~/.bun/install/cache. - C2 pinning — known command-and-control domains (
masscan.cloud,getsession.org,webhook.site,sfrclak.com, …) in/etc/hosts. - Shell history —
~/.zsh_history/~/.bash_history/~/.historyfor worm-related strings (with filtering so that cloning a defensive detector repo doesn't trip a false positive). - Persistence — launchd plists (macOS),
crontab, and shell rc files (~/.zshrc,~/.bashrc,~/.zprofile, …) that reference worm artifacts or pipe a remote script straight into a shell. - npm token marker — the
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwnerstring in.npmrcfiles. - Running processes — live
node/bun/trufflehogprocesses running worm filenames. - AI-agent configs —
.claude/.gemini/.cursor/ VS Code config files inspected for malicious content (these are common launcher vectors; clean ones are still surfaced for review). - Compromised packages — your npm cache index and globally-installed packages cross-checked against the published list of compromised package names, scopes, and pinned versions.
- "Phantom-gyp" — suspiciously tiny
binding.gypfiles (≈157 bytes) that smuggle a command substitution, the install-time vector used by the Miasma variant.
- Repo name / Git refs — directory names and branch/tag refs containing
shai-hulud/sha1-hulud. - IOC files — dropper and workflow files (
setup_bun.js,bun_environment.js,router_init.js,router_runtime.js,.github/workflows/shai-hulud-workflow.yml,.github/workflows/discussion.yaml) in the working tree and in Git history (so an artifact that was committed and later removed is still caught). - Worm-signature commits — commits authored as
claude@users.noreply.github.comsince 2025-09-01, a fingerprint the worm leaves behind. - Launcher configs —
.vscode/tasks.json,settings.json,setup.mjs,package.json,binding.gyp, etc. inspected for malicious content signatures (base64/eval/child_process, pipingcurl/wgetinto a shell,/dev/tcp/, suspicious install URLs, …). - Dependency cross-check — every
package.json's dependencies matched against the compromised-package IOC lists, including pinned bad versions such asaxios@1.14.1/axios@0.30.4.
A final pass looks for dropper payloads (setup_bun.js, bun_environment.js, router_init.js,
router_runtime.js) installed inside node_modules, i.e. outside any Git working tree.
| Tag | Meaning |
|---|---|
[hit] (red) |
Confirmed IOC — a distinctive worm artifact. Treat the machine/repo as compromised. |
[suspect] (yellow) |
A launcher-vector config, unexpected binary, or odd config whose contents or context match a malicious pattern. Investigate. |
[review] (dim) |
A benign-looking artifact (e.g. an AI-agent config that scanned clean) — surfaced so it is never silently ignored. |
- Bash — works with the stock macOS
/bin/bash(3.2) as well as modern Bash. - git, python3, and standard Unix tools (
find,grep,awk,sed,sort,xargs,stat,ps,wc,tr,mktemp,crontab) — all present by default on macOS and most Linux. - npm — optional; only used to enumerate globally-installed packages.
bunandtrufflehogare not dependencies. They are indicators the scanner looks for — if you don't use them and the scanner finds them, that is itself a signal.
Platform: macOS-first (launchd checks, BSD stat), and fully usable on Linux — the
macOS-specific checks simply find nothing there.
git clone https://github.com/Eric-Jalal/shai-hulud-repo-scanner.git
cd shai-hulud-repo-scanner
chmod +x scan.shOr grab just the script:
curl -fsSL -o scan.sh https://raw.githubusercontent.com/Eric-Jalal/shai-hulud-repo-scanner/main/scan.sh
chmod +x scan.shscan.sh [root-dir] scan ROOT and every Git repo under it (default: $HOME)
scan.sh --no-host skip the once-per-machine host checks
There is no --help; any unrecognized flag exits 2.
./scan.sh # full sweep of your home directory
./scan.sh ~/code # scan a specific tree
./scan.sh --no-host ~/code # per-repo checks only (good for re-scans / CI)Findings go to stdout; the live progress counter goes to stderr, so you can save a clean report:
./scan.sh > findings.txt # progress stays on screen, findings land in the fileOutput is colorized with ANSI codes. To strip them when saving to a file:
./scan.sh | sed $'s/\033\\[[0-9;]*m//g' > findings.txt| Code | Meaning |
|---|---|
0 |
Clean — no hits or suspects (any [review] items are informational only). |
1 |
At least one [hit] or [suspect] was found. |
2 |
Invalid command-line flag. |
This makes it easy to gate a script or CI step on ./scan.sh --no-host . || echo "review needed".
A clean run:
scanning .
clean — 1 repos scanned
An illustrative run with findings (format only — not a real infection):
scanning /Users/you
host: binary, persistence, cache, history checks
[suspect] binary 'trufflehog' present at /usr/local/bin/trufflehog (worm-installed if you don't use it)
[hit] worm-path present: /Users/you/.truffler-cache
[review] /Users/you/.claude/settings.json
[hit] working tree: /Users/you/code/acme-app/.github/workflows/shai-hulud-workflow.yml
[hit] /Users/you/code/acme-app/package.json depends on pinned-compromised version: axios@1.14.1
3 confirmed hit(s)
1 suspect item(s) — inspect the lines above
1 item(s) listed for manual review
5 repos scanned
A confirmed hit means worm artifacts are present. Treat the affected account/machine as compromised and, at minimum:
- Rotate every credential the machine could reach — npm tokens, GitHub PATs/SSH keys, and cloud (AWS/GCP/Azure) keys. Do this from a known-clean device.
- Audit your GitHub account for unexpected public repos (exfil targets), new workflows, and recently published package versions.
- Remove the artifacts and any persistence (launchd/cron/rc entries) the scanner pointed at.
- Reinstall dependencies from clean lockfiles after pinning away from compromised versions, and consider rebuilding the host if host-level hits appeared.
This tool detects; it does not remediate. For full incident-response guidance, follow the vendor writeups linked below.
The indicators live as editable constants near the top of scan.sh — update them as new research
lands:
COMPROMISED_EXACT,COMPROMISED_SCOPES,COMPROMISED_VERSIONED— the compromised-package lists.IOC_PATHS,IOC_REFS,IOC_NAME— confirmed-artifact filenames and ref/name patterns.STRONG— the malicious-content signature regex used to inspect config files.
Known false positive: this repository's own directory name contains
shai-hulud, which matches the repo-name IOC. Scanning any tree that includes a clone of this scanner will report[hit] repo name: …/shai-hulud-repo-scanner— that is expected. Likewise, a flagged scope may include healthy package versions; verify the specific version before assuming the worst.
The IOC lists are assembled from public research, including:
- Cobenian/shai-hulud-detect
- gensecaihq/Shai-Hulud-2.0-Detector
- security writeups from Wiz, Datadog, JFrog, Snyk, and StepSecurity.
This is a best-effort detection aid based on a curated subset of public IOCs. A clean result is not a guarantee of safety, and it is not a substitute for a full incident-response investigation. Use at your own risk.
Released under the MIT License. (No separate LICENSE file is committed yet — tell me the
name/year for the copyright line if you'd like one added, or pick a different license.)