
Defense Against the Shai-Hulud Supply Chain Attack
A security tool to detect compromised npm packages from the Shai-Hulud supply chain attacks (original September 2025 and Shai-Hulud 2.0 November 2025). Scans both package.json and package-lock.json files to detect exact installed versions and Indicators of Compromise (IoCs). Available in both Python and Node.js implementations with centralized YAML configuration for easy maintenance.
Latest Update: November 24, 2025 - 738+ compromised packages tracked with 1,291 unique package@version combinations. Now detects both original Shai-Hulud and Shai-Hulud 2.0 attack variants.
For multi-ecosystem scanning, see ore-mal-pkg-inspector.
For multi-ecosystem malicious package scanning (npm, PyPI, Maven, RubyGems, Go, Cargo), see: ore-mal-pkg-inspector - Comprehensive malicious package scanner with dynamic threat intelligence.
OreNPMGuard focuses specifically on Shai-Hulud attack defense for npm packages.
Shai-Hulud is a self-replicating worm that began compromising npm packages on September 14-15, 2025, representing the first successful self-propagating attack in the npm ecosystem and one of the most severe JavaScript supply-chain attacks observed to date. Named after the giant sandworms from Frank Herbert's Dune series, this malware has evolved into multiple variants.
The original attack infected 200+ npm packages (as tracked in this tool) with multiple versions affected per package.
A new variant emerged in November 2025 with significant changes:
preinstall scripts (not just postinstall)setup_bun.js and bun_environment.js (in addition to bundle.js)Reference: Wiz Research - Shai-Hulud 2.0 Blog Post
Patient Zero: The attack started with the rxnt-authentication package published on September 14, 2025, at 17:58:50 UTC by the compromised "techsupportrxnt" npm account.
Attack Chain (Original Shai-Hulud):
postinstall script executing bundle.js (3MB+ JavaScript payload)data.json (double base64-encoded).github/workflows/shai-hulud-workflow.yml) that exfiltrates repository secrets to webhook[.]sitepostinstall hooksAttack Chain (Shai-Hulud 2.0 - November 2025):
preinstall script executing setup_bun.js or bun_environment.js (new payload files)cloud.json, contents.json, environment.json, and truffleSecrets.json files.github/workflows/discussion.yaml with self-hosted runner for backdoor access.github/workflows/formatter_*.yml for secret exfiltration (then deletes workflow to hide activity)docker run --rm --privileged -v /:/hostghp_*, gho_*), npm authentication tokensOriginal Shai-Hulud (September 2025):
@ctrl/tinycolor, ngx-bootstrap)Shai-Hulud 2.0 (November 2025):
Verified Credential Theft (from ~20,000 analyzed repos):
CRITICAL: Wiz Research has confirmed cross-victim exfiltration is occurring. This means:
Investigation Implication: When reviewing exfiltrated data in your repositories, verify whether the data actually belongs to your organization or another victim.
This attack is directly linked to the August 2025 s1ngularity/Nx compromise, where initial GitHub token theft enabled the broader supply chain attack. Many initial Shai-Hulud victims were known victims of the s1ngularity attack. Security researchers also note the integration of AI-generated content within the campaign, with moderate confidence that an LLM was used to generate the malicious bash script.
Original Shai-Hulud Campaign:
rxnt-authentication published ("Patient Zero")Shai-Hulud 2.0 Campaign:
PyYAML (pip install pyyaml)js-yaml (npm install js-yaml)# Make executable
chmod +x shai_hulud_scanner.py
# Scan single package.json
python3 shai_hulud_scanner.py ./package.json
# Scan package-lock.json for exact versions
python3 shai_hulud_scanner.py ./package-lock.json
# Scan entire project directory
python3 shai_hulud_scanner.py ./my-project
# Scan current directory
python3 shai_hulud_scanner.py .
# Install dependencies first
npm install
# Make executable
chmod +x shai_hulud_scanner.js
# Scan single package.json
node shai_hulud_scanner.js ./package.json
# Scan package-lock.json for exact versions
node shai_hulud_scanner.js ./package-lock.json
# Scan entire project directory
node shai_hulud_scanner.js ./my-project
# Scan current directory
node shai_hulud_scanner.js .
✅ Exact Match Detection: Identifies packages with exact version matches to known compromised versions
⚠️ Potential Risk Detection: Flags packages with the same name but different versions (may still be at risk)
🔍 Dual File Support: Scans both package.json (declared dependencies) and package-lock.json (exact installed versions)
📦 Comprehensive Coverage: package-lock.json scanning includes nested dependencies and transitive packages
🔄 Recursive Scanning: Automatically scans all subdirectories while skipping node_modules
📋 Detailed Reporting: Shows package names, versions, dependency sections, and affected versions
🔎 IOC Detection: Identifies Indicators of Compromise for both original Shai-Hulud and Shai-Hulud 2.0:
"postinstall": "node bundle.js" hooks, bundle.js files, shai-hulud-workflow.yml"preinstall" hooks, setup_bun.js, bun_environment.js, discussion.yaml, formatter_*.yml workflowswebhook.site exfiltration endpoints, SHA1HULUD runner patterns, Docker privilege escalation🚨 Critical (Compromised Packages Found):
🚨 CRITICAL: Found 2 CONFIRMED compromised packages:
• @ctrl/deluge v7.2.2 in dependencies
Affected versions: 7.2.2, 7.2.1
• ngx-bootstrap v19.0.3 in devDependencies
Affected versions: 18.1.4, 19.0.3, 20.0.4, 20.0.5, 20.0.6, 19.0.4, 20.0.3
⚠️ Warning (Version Mismatch):
⚠️ WARNING: Found 1 packages with different versions:
• @ctrl/deluge v7.2.0 in dependencies
Known affected versions: 7.2.2, 7.2.1
✅ Clean:
✅ No affected packages found
npm uninstall <package-name>npm cache clean --forcerm -rf node_modules"postinstall": "node bundle.js" in package.json filesbundle.js files (3MB+ in size, contains malicious payload).github/workflows/shai-hulud-workflow.yml files"preinstall" hooks in package.json filessetup_bun.js and bun_environment.js payload filescloud.json, contents.json, environment.json, truffleSecrets.jsonPackage data is centralized in affected_packages.yaml. To add new compromised packages:
affected_packages.yaml- name: "package-name"
versions: ["1.0.0", "1.0.1"]
This centralized approach eliminates the need to update multiple files when new threats are discovered.
# Create central scanning script
curl -O https://your-domain.com/shai_hulud_scanner.py
chmod +x shai_hulud_scanner.py
# Mass scan multiple projects
for dir in /projects/*/; do
echo "Scanning $dir"
python3 shai_hulud_scanner.py "$dir"
done
# GitHub Actions example
- name: Scan for Shai-Hulud packages
run: |
curl -O https://your-domain.com/shai_hulud_scanner.js
npm install js-yaml
node shai_hulud_scanner.js .
if [ $? -ne 0 ]; then
echo "SECURITY ALERT: Compromised packages detected!"
exit 1
fi
# Add to security toolkit
cp shai_hulud_scanner.py /usr/local/bin/
cp shai_hulud_scanner.js /usr/local/bin/
cp affected_packages.yaml /usr/local/bin/
# Create alias for easy access
echo 'alias scan-shai="python3 /usr/local/bin/shai_hulud_scanner.py"' >> ~/.bashrc
postinstall, Shai-Hulud 2.0 uses preinstall (increases exposure in build environments)Original Shai-Hulud (September 2025):
"postinstall": "node bundle.js" in package.jsonbundle.js (typically 3MB+ minified JavaScript)webhook.site domains.github/workflows/shai-hulud-workflow.yml for persistenceshai-hulud containing malicious commitsShai-Hulud 2.0 (November 2025):
"preinstall": "node setup_bun.js" or "preinstall": "node bun_environment.js" in package.jsonsetup_bun.js, bun_environment.js (in addition to bundle.js)cloud.json, contents.json, environment.json, truffleSecrets.json.github/workflows/discussion.yaml (with runs-on: self-hosted and RUNNER_TRACKING_ID: 0).github/workflows/formatter_*.yml (pattern matching, used for secret exfiltration)Common to Both Variants:
webhook.site domainspreinstall scripts (Shai-Hulud 2.0 execution phase)setup_bun.js and bun_environment.jscloud.json, contents.json, environment.json, truffleSecrets.jsondiscussion.yaml and formatter_*.yml workflow patternsprevention/block-shai-hulud.shFor issues or questions:
.github/workflows/discussion.yaml (with self-hosted runner).github/workflows/formatter_*.yml filesdocker run --rm --privileged -v /:/host)webhook.site references in code or network logsdocker run --rm --privileged -v /:/hostwebhook.site domains (same as original)