This repository contains a tool for detecting and investigating the September 2025 NPM compromises that affected 212 packages across three major incidents, including the critical S1ngularity/Shai Hulud self-propagating worm attack. The malware targets cryptocurrency wallets, exfiltrates secrets, and can automatically propagate through GitHub Actions.
- API-Based Repository Scanning: Full support for JFrog Artifactory and NPM Registry
- Non authenticated scan of know packages contained in a json file definistion
- Deep Scan Capability: Downloads and analyzes actual package contents for obfuscation patterns
- Comprehensive Mode (-All): Scans extended package lists (45 packages for NPM Registry)
- Cross-Platform Support: Works on Windows, macOS, and Linux with PowerShell
- Professional Reporting: JSON and CSV export with HTML report generation
- 212 Malicious Package Detection: Complete database including Shai Hulud worm (187 packages)
- Removed unreliable HTML scraping functionality for cleaner operation
- Enhanced deep scanning with actual package tarball downloads
- Improved error handling and cross-platform compatibility
- Updated malicious package database (18 โ 212 packages, including 187 from Shai Hulud worm)
Incident Timeline: September 8-16, 2025 Total Affected Packages: 212 packages across 3 incidents
- Sept 8: Initial compromise (18 packages: debug, chalk, etc.)
- Sept 9: DuckDB packages (4 packages)
- Sept 16: S1ngularity/Shai Hulud Worm (187 packages with self-propagation) Attack Vectors: Browser wallet hijacking, GitHub Actions manipulation, secret exfiltration Critical Update: Shai Hulud worm can self-propagate through npm and GitHub
| File | Description |
|---|---|
Scan-NPMRepository.ps1 |
Crimson7 NPM Security Scanner (PowerShell) |
Generate-HTMLReport.ps1 |
Crimson7 HTML report generator with branded output |
malicious_packages.json |
Database of compromised packages and signatures |
NPM_Compromise_Timeline.md |
Complete incident timeline and analysis |
npm_threat_hunting_runbook_final.md |
KQL queries for Microsoft Sentinel |
NPM_Compromise_Executive_Summary_and_Report.md |
Executive report with findings |
- PowerShell 5.1 or higher (Windows) or PowerShell Core 7+ (cross-platform)
- Network access to your NPM repository
- (Optional) API credentials for authenticated repository access
- Clone or download this repository:
git clone https://github.com/your-org/npm-compromise-scanner.git
cd npm-compromise-scanner- Ensure all three core files are in the same directory:
Scan-NPMRepository.ps1malicious_packages.jsonNPM_Compromise_Timeline.md
Scan your local projects for compromised packages:
.\Scan-NPMRepository.ps1 -LocalPath "C:\Projects\YourApp"With deep scanning for obfuscation patterns:
.\Scan-NPMRepository.ps1 -LocalPath "C:\Projects\YourApp" -DeepScanScan extended package lists including popular packages (resource intensive):
NPM Registry - scans 45 packages (25 malicious + 20 popular):
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://registry.npmjs.org/" -DeepScan -AllJFrog Artifactory - attempts to scan all packages in repository:
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/artifactory/npm-repo/" -DeepScan -All -ApiKey "YOUR_KEY"Use the API or repository URL, NOT the UI URL:
โ CORRECT - API URL (Recommended):
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/artifactory/api/npm/npm-remote/"โ CORRECT - Direct Repository URL:
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/artifactory/npm-remote/"โ WRONG - UI URL (This won't work):
# Don't use URLs with /ui/native/ - these are for browser access only
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/ui/native/npm-remote/.npm"-
From the UI URL in your browser:
- If you see:
https://artifactory.company.com/ui/native/npm-remote/.npm - Use this:
https://artifactory.company.com/artifactory/api/npm/npm-remote/
- If you see:
-
General pattern:
- UI URL:
https://[server]/ui/native/[repo-name]/ - API URL:
https://[server]/artifactory/api/npm/[repo-name]/ - Direct URL:
https://[server]/artifactory/[repo-name]/
- UI URL:
-
Test your URL:
# Test if the URL is correct (should return JSON) curl https://artifactory.company.com/artifactory/api/npm/npm-remote/
# With API key (recommended for private repositories)
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/artifactory/api/npm/npm-remote/" -ApiKey "AKCp5...".\Scan-NPMRepository.ps1 -RepositoryUrl "https://registry.npmjs.org/"Scan both local and remote simultaneously:
.\Scan-NPMRepository.ps1 -LocalPath "." -RepositoryUrl "https://artifactory.company.com/artifactory/npm-repo/" -ApiKey "YOUR_KEY" -DeepScan- Public repositories: API key is NOT required
- Private repositories: API key is REQUIRED
- Benefits of using API key:
- Access to private packages
- Higher rate limits
- Detailed version information
- Audit trail of scans
- Log into your JFrog Artifactory instance
- Click on your username (top right) โ Edit Profile
- Generate an API Key under "Authentication Settings"
- Use with
-ApiKeyparameter
- Public packages: No authentication needed
- Private packages: Use NPM token as API key
| Parameter | Required | Description | Example |
|---|---|---|---|
-LocalPath |
No | Local directory to scan | -LocalPath "C:\Projects\MyApp" |
-RepositoryUrl |
No | Remote repository URL | -RepositoryUrl "https://registry.npmjs.org/" |
-ApiKey |
No | Authentication key for private repos | -ApiKey "AKCp5..." |
-DeepScan |
No | Download and scan package contents | -DeepScan |
-All |
No | Scan extended package list including popular packages ( |
-All |
-OutputPath |
No | Custom output directory | -OutputPath "C:\Reports" |
-Verbose |
No | Detailed logging | -Verbose |
-Allparameter scans extended package lists:- JFrog Artifactory: Attempts to scan every package in repository
- NPM Registry: Scans 25 malicious + 20 popular packages (45 total)
-Allscanning is resource-intensive and may take several minutes for Jfrog-DeepScandownloads actual package files to analyze JavaScript content- Combine
-Alland-DeepScanonly for small repositories or targeted investigations - Use
-Verbosefor troubleshooting connection or parsing issues
The scanner generates three types of output:
- Console Output - Color-coded real-time results:
- ๐ด CRITICAL (Red): Malicious packages found
- ๐ก SUSPICIOUS (Yellow): Obfuscation patterns detected
- ๐ต CHECK (Cyan): Manual review needed
- ๐ข CLEAN (Green): No issues found
-
Exact Version Matching
- All 212 compromised packages with specific malicious versions
- Original 18 packages: chalk@5.6.1, debug@4.4.2, ansi-styles@6.2.2
- DuckDB packages: duckdb@1.3.3, @duckdb/node-api@1.3.3
- Shai Hulud worm: 187 packages including @crowdstrike/, @ctrl/, @nativescript-community/*
- Additional packages: @coveops/abi@2.0.1, prebid@10.9.1/10.9.2
-
Obfuscation Patterns
const _0x112signature pattern- Heavy hex-variable obfuscation (
_0x[0-9a-f]{4,}) - Malware function names (
checkethereumw,stealthProxyControl) - Worm propagation patterns (GitHub Actions manipulation)
- Token stealing patterns (npm, GitHub, environment variables)
-
Cryptocurrency Targeting
- Multiple wallet addresses in single file
- Levenshtein distance algorithm (used for address swapping)
- Wallet-related keywords and APIs
-
Known Malicious Hashes
- SHA1: e9f9235f0fd79f5a7d099276ec6a9f8c5f0ddce9 (error-ex)
- And 4 other confirmed malicious file hashes known on the public internet TLP-WHITE
- Cannot detect if malicious code has already been bundled into production builds
- May produce false positives for legitimate obfuscated code
- Requires file system access for deep scanning
- API rate limits may affect large repository scans
| Status | Meaning | Action Required |
|---|---|---|
| MALICIOUS | Confirmed compromised package version | Immediate removal and remediation |
| SUSPICIOUS | Contains obfuscation patterns or wallet code | Manual review and testing |
| CHECK_REQUIRED | Unable to determine, parsing errors | Manual investigation |
| CLEAN | No indicators found | No action needed |
0- No threats detected1- Suspicious packages found2- Malicious packages confirmed
If malicious packages are detected:
-
Immediate Actions
# Stop all builds # Remove node_modules rm -rf node_modules # Clear npm cache npm cache clean --force # Remove package-lock.json rm package-lock.json
-
Clean Installation
# Update package.json to safe versions # Reinstall with exact versions npm install --save-exact # Audit for vulnerabilities npm audit fix
-
Verification
# Re-run scanner to confirm clean .\Scan-NPMRepository.ps1 -LocalPath "." -DeepScan
.\Scan-NPMRepository.ps1 -LocalPath "." -OutputPath "C:\SecurityReports"# Create scheduled task for daily scans
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\Scan-NPMRepository.ps1 -LocalPath C:\Projects -DeepScan"
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
Register-ScheduledTask -TaskName "NPM Security Scan" -Action $action -Trigger $trigger(not sure you'll be needing this without constantly updating the malicious_packages.json)
# Azure DevOps Pipeline
- task: PowerShell@2
displayName: 'NPM Security Scan'
inputs:
filePath: 'Scan-NPMRepository.ps1'
arguments: '-LocalPath $(Build.SourcesDirectory) -DeepScan'
continueOnError: false| Package | Malicious Version | Downloads (millions) |
|---|---|---|
| debug | 4.4.2 | 357.6 |
| chalk | 5.6.1 | 299.99 |
| ansi-styles | 6.2.2 | 371.41 |
| strip-ansi | 7.1.1 | - |
| supports-color | 10.2.1 | - |
| is-arrayish | 0.3.3 | 73.8 |
| error-ex | 1.3.3 | - |
| simple-swizzle | 0.2.3 | 26.26 |
| Package | Malicious Version | Weekly Downloads |
|---|---|---|
| duckdb | 1.3.3 | 148,000 |
| @duckdb/node-api | 1.3.3 | 87,000 |
| @duckdb/node-bindings | 1.3.3 | 87,000 |
| @duckdb/duckdb-wasm | 1.29.2 | 64,000 |
| Package | Malicious Version | Status |
|---|---|---|
| @coveops/abi | 2.0.1 | Quickly removed |
| proto-tinker-wc | 0.1.87 | Low impact |
| prebid | 10.9.1, 10.9.2 | Multiple versions |
187 packages including:
- @crowdstrike/ packages (9 packages: commitlint, falcon-shoelace, foundry-js, etc.)
- @ctrl/ packages (16 packages: deluge, ngx-codemirror, react-adsense, etc.)
- @nativescript-community/ packages (20+ packages)
- @operato/ packages (13 packages)
- And 140+ more packages with self-propagation capability
Full details in malicious_packages.json
- Executes in browser context
- Hooks wallet transaction APIs
- Swaps recipient addresses before signing
- Uses visually similar addresses (Levenshtein algorithm)
- No C2 communication (uses hardcoded addresses)
- Self-propagation: Automatically spreads to new packages
- GitHub Actions manipulation: Modifies workflows for persistence
- Secret exfiltration: Steals npm tokens, GitHub tokens, environment variables
- C2 Communication: Connects to 185.174.137.80
- Persistence: Modifies browser extensions and CI/CD pipelines
Issue: "Cannot access repository"
# Solution: Check API key and URL format
.\Scan-NPMRepository.ps1 -RepositoryUrl "https://artifactory.company.com/artifactory/api/npm/npm-repo/" -ApiKey "YOUR_KEY"Issue: "Script execution policy error"
# Solution: Temporarily bypass execution policy
powershell -ExecutionPolicy Bypass -File .\Scan-NPMRepository.ps1 -LocalPath "."Issue: "Rate limit exceeded"
# Solution: Add delay between requests or use API key
.\Scan-NPMRepository.ps1 -RepositoryUrl "URL" -ApiKey "KEY"If you find false positives or false negatives, please report them with:
- Package name and version
- Scanner output
- package.json contents
Check for updates to malicious_packages.json as new threats emerge.
- Aikido Security Blog - Initial Compromise
- Aikido Security Blog - Shai Hulud Worm
- Malware Analysis
- HackerNews Discussion
- AlienVault OTX Pulse
This tool is provided as-is for security scanning purposes. Use responsibly and in accordance with your organization's security policies.
Advanced Supply Chain Security Analysis
Last Updated: September 19, 2025
Version: 1.2.0 (Shai Hulud Update)
Status: Production Draft - API-Based Scanning Only
Package Database: 212 malicious packages tracked (including 187 from Shai Hulud worm)