Shai-Hallud NPM Worm scanner for GitHub repositories. Scans organization or user repositories for vulnerable npm packages by checking package manifests and lock files against an IOC (Indicators of Compromise) database.
- π Scans all repositories in a GitHub organization or user account
- π¦ Supports multiple package managers and lock files:
- npm:
package.json,package-lock.json,npm-shrinkwrap.json - Yarn:
yarn.lock(v1 classic format) - pnpm:
pnpm-lock.yaml(v6+ format)
- npm:
- π³ Enumerates all dependencies including transitive (nested) dependencies
- π‘οΈ Checks against multiple vulnerability databases (DataDog + Wiz IOC lists by default)
- π¨ Detects malicious migration repositories (
*-migrationwith "Shai-Hulud Migration" description) - πΏ Detects malicious
shai-huludbranches - π Detects malicious GitHub Actions workflows (discussion.yaml pattern)
- π Detects malicious npm lifecycle scripts (
node bundle.jsin postinstall, etc.) - β±οΈ Conservative rate limiting to avoid GitHub API limits
- π¨ Colored terminal output with emoji indicators
- π Summary reports with affected repository listings
# Clone the repository
git clone https://github.com/rslater/muaddib.git
cd muaddib
# Build the binary
go build -o muaddib ./cmd/muaddib/Muaddib requires a GitHub Personal Access Token to access the GitHub API. Follow these steps to create a token with minimal required permissions.
-
Go to GitHub β Settings β Developer settings β Personal access tokens β Fine-grained tokens
-
Click "Generate new token"
-
Configure the token:
Setting Value Token name muaddib-scanner(or any descriptive name)Expiration Choose based on your needs (recommend 7 days) Description Optional: "NPM vulnerability scanner" -
Resource owner: Select the organization or your personal account you want to scan
-
Repository access: Choose one of:
- "All repositories" - To scan all repos in the org/account
- "Only select repositories" - To limit scope to specific repos
-
Permissions (expand "Repository permissions"):
Permission Access Level Why Needed Contents Read-onlyTo read package.jsonandpackage-lock.jsonfilesMetadata Read-onlyTo list repositories (automatically selected) β οΈ No other permissions are required. Leave all others as "No access". -
Click "Generate token"
-
Copy the token immediately - it won't be shown again!
Important
Treat your GitHub token like a password. Keep it secret and secure. While it might be tempting to simply export GITHUB_TOKEN=your_token_here in your shell, this can expose the token in shell history or process listings. Instead, consider using a password manager to store and retrieve the token securely (Option B).
Create a .env file that is not committed to version control:
# Create the file with restricted permissions
touch ~/.muaddib.env
chmod 600 ~/.muaddib.env
# Add your token with the format export GITHUB_TOKEN=github_pat_xxxx
vi ~/.muaddib.envyour .muaddib.env file should contain:
export GITHUB_TOKEN=github_pat_your_generated_token_hereSource it when needed:
source ~/.muaddib.env
./muaddib --org mycompanyFor enhanced security, retrieve the token from a secret manager:
# Example with 1Password CLI
export GITHUB_TOKEN=$(op read "op://Private/GitHub PAT/token")
# Example with Bitwarden CLI
export GITHUB_TOKEN=$(bw get password "github-muaddib-token")
# Example with macOS Keychain
export GITHUB_TOKEN=$(security find-generic-password -a "$USER" -s "github-muaddib" -w)
# Example with pass (Unix password manager)
export GITHUB_TOKEN=$(pass show github/muaddib-token)./muaddib --org your-org-name --verboseYou should see:
β
Loaded X IOC entries (Y unique packages)
π Connected to GitHub API (rate limit: 1.0 req/sec)
π¦ Fetching repositories for organization: your-org-name
-
Never commit tokens to version control
echo ".env" >> .gitignore echo "*.env" >> .gitignore
-
Use fine-grained tokens instead of classic tokens - they have narrower scope
-
Set expiration dates - rotate tokens regularly
-
Use the minimum required permissions - only
Contents: Readis needed -
Revoke tokens when not in use - delete them from Settings β Tokens
-
For CI/CD pipelines, use:
- GitHub Actions: Repository or organization secrets
- GitLab CI: Protected CI/CD variables
- Other CI: Dedicated secrets management
# Scan an organization
./muaddib --org mycompany
# Scan a user's repositories
./muaddib --user johndoe
# Verbose output (shows progress)
./muaddib --org mycompany --verbose# Use a custom vulnerability CSV (replaces default sources)
./muaddib --org mycompany --vuln-csv ./my-iocs.csv
# Slower rate limit (for large orgs or to be extra safe)
./muaddib --org mycompany --rate-limit 0.5
# Skip devDependencies
./muaddib --org mycompany --skip-dev
# Combine options
./muaddib --org mycompany --verbose --rate-limit 0.5 --skip-dev| Flag | Default | Description |
|---|---|---|
--org |
- | GitHub organization to scan |
--user |
- | GitHub user to scan |
--vuln-csv |
DataDog + Wiz IOC lists | Path or URL to vulnerability CSV (custom) |
--rate-limit |
1.0 |
API requests per second |
--skip-dev |
false |
Skip devDependencies |
--verbose |
false |
Enable detailed progress output |
The tool accepts CSV files in two formats:
package_name,package_versions,sources
malicious-package,1.0.0,datadog
another-bad-pkg,"2.3.4, 2.3.5",datadog
compromised-lib,1.2.3,datadog- Version field uses comma-separated list:
"1.0.0, 1.0.1, 1.0.2" - Column names:
package_name,package_versions
Package,Version
malicious-package,= 1.0.0
another-bad-pkg,= 2.3.4 || = 2.3.5
compromised-lib,= 1.2.3- Version field uses npm semver exact match syntax:
= X.Y.Z || = A.B.C - Column names:
Package,Version
The parser automatically detects column names using case-insensitive matching:
- Package name column:
package_name,packagename,name,package, orPackage - Version column:
package_versions,package_version,packageversion,version,versions, orVersion
If column headers are not recognized, the parser falls back to positional parsing:
- Column 1: Package name
- Column 2: Version
When fallback parsing is used, a warning is displayed with sample data to help verify correctness.
By default, Muaddib loads both IOC lists simultaneously:
- DataDog IOC list - Primary source
- Wiz IOC list - Secondary source
The databases are merged and deduplicated automatically. This provides the most comprehensive coverage of known malicious packages.
__ __ _ _ _ _ _
| \/ | _ _ __ _ __| |( ) __| |(_)| |__
| |\/| || || | / _` |/ _` ||/ / _` || || '_ \
| | | || _,_|| (_| || (_| | | (_| || || |_) |
|_| |_| \__,_|\__,_| \__,_| \__,_||_||_.__/
Shai-Hulud NPM Worm Scanner for GitHub
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π₯ Loading vulnerability database...
Using default sources: DataDog + Wiz IOC lists
β
Loaded 2180 IOC entries (795 unique packages, 1091 vulnerable versions)
π Connected to GitHub API (rate limit: 1.0 req/sec)
π¦ Fetching repositories for organization: example-org
β
Found 25 repositories
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Repository: example-org/vulnerable-app
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ Scanned 2 files, found 847 unique packages
π΄ Found 3 issue(s):
π Malicious Script Detected:
π΄ package.json
Script: postinstall β node bundle.js
Pattern: node bundle.js
π package-lock.json:
π΄ malicious-pkg@1.2.3 [transitive]
π΄ bad-dependency@4.5.6 (dev) [transitive]
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SCAN SUMMARY
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Repositories scanned: 25
π¦ Total packages checked: 12847
π IOC database entries: 156
π΄ Vulnerable packages found: 2
π Malicious scripts found: 1
β οΈ Affected repositories: 1
Affected repositories:
π΄ example-org/vulnerable-app (2 vulnerable, 1 malicious script)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The following references were used in building this tool, all credit for detecting instances of Shai-Haluld infection should go to the companies and authors below:
- GitLab discovers widespread npm supply chain attack by Daniel Abeles and Michael Henriksen (GitLab),
- The Shai-Hulud 2.0 npm worm: analysis, and what you need to know by Christophe Tafani-Dereeper and Sebastian Obregoso (DataDog),
- Shai-Hulud 2.0: How Cortex Detects and Blocks the Resurgent npm Worm by Cameron Hyde and Yitzy Tannenbaum (Palo Alto Networks)
- Shai-Hulud 2.0 Supply Chain Attack: 25K+ Repos Exposing Secrets by Hila Ramati, Merav Bar, Gal Benmocha, Gili Tikochinski (wiz.io)
- Widespread Supply Chain Compromise Impacting npm Ecosystem by CISA
- Post-mortem of Shai-Hulud attack on November 24th, 2025 by Oliver Browne (PostHog)
- Shai-hulud npm attack: What you need to know by Karlo Zanki (Reversing Labs)
- Shai Hulud 2.0: The NPM Supply Chain Attack Returns as an Aggressive Self-Propagating Worm by Koby Turjeman
MIT