Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

shai-hulud-repo-scanner

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).

platform shell license

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.


Background

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.


What it checks

Host checks (run once per machine)

  • Worm toolingbun and trufflehog binaries on your PATH (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 / ~/.history for 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 IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner string in .npmrc files.
  • Running processes — live node/bun/trufflehog processes 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.gyp files (≈157 bytes) that smuggle a command substitution, the install-time vector used by the Miasma variant.

Per-repo checks (every Git repo under the scan root)

  • 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.com since 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, piping curl/wget into 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 as axios@1.14.1 / axios@0.30.4.

node_modules dropper sweep

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.


Severity tiers

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.

Requirements

  • 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.
  • npmoptional; only used to enumerate globally-installed packages.

bun and trufflehog are 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.


Install

git clone https://github.com/Eric-Jalal/shai-hulud-repo-scanner.git
cd shai-hulud-repo-scanner
chmod +x scan.sh

Or 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.sh

Usage

scan.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 file

Output is colorized with ANSI codes. To strip them when saving to a file:

./scan.sh | sed $'s/\033\\[[0-9;]*m//g' > findings.txt

Exit codes

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".


Example output

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

If you get a [hit]

A confirmed hit means worm artifacts are present. Treat the affected account/machine as compromised and, at minimum:

  1. 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.
  2. Audit your GitHub account for unexpected public repos (exfil targets), new workflows, and recently published package versions.
  3. Remove the artifacts and any persistence (launchd/cron/rc entries) the scanner pointed at.
  4. 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.


Tuning the IOC lists

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.


Credits / IOC sources

The IOC lists are assembled from public research, including:


Disclaimer

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.


License

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.)

About

Hardened scanner that searches all the reported IOC and strange branches on local.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages