detecting npm chalk/debug compromise: automated scanner for september 2025 supply chain attack
on this page
tl;dr
Quick scan (pipx): | pipx run npm-vuln-scanner --help |
Quick scan (uvx): | uvx npm-vuln-scanner --help |
Install: | pip install npm-vuln-scanner |
Packages affected: | 26 packages (~2.5B downloads/week), only 10 fixed |
Critical unfixed: | debug 4.4.2, color-convert 3.1.1, duckdb 1.3.3, 13 others |
Scanner: | PyPI | GitHub |
JSON output: | npm-vuln-scanner --json scan | jq '.summary' |
summary
On September 8-9, 2025, attackers compromised 26 npm packages totaling ~2.5 billion weekly downloads through the npmjs.help phishing campaign. The attack affected packages from multiple maintainers including DuckDB (CVE-2025-59037), suggesting broader account compromise beyond just the chalk organization. Malicious code intercepted cryptocurrency transactions in browser environments.
Critical: Only 10 of 26 packages (chalk-maintained) have clean versions available. 16 packages remain without fixes including critical packages like debug, DuckDB packages, and others.
Automated scanner v1.2.0 available: pip install npm-vuln-scanner
or pipx run npm-vuln-scanner scan
- Now detects all 26 compromised packages
timeline
- September 5, 2025: Phishing domain
npmjs.help
registered - September 8, 2025 13:00 UTC: Malicious versions published to 22 packages across multiple maintainers
- September 8, 2025 ~15:00 UTC: Compromise discovered, packages removed (~2 hour exposure window)
- September 8, 2025 16:58 UTC: proto-tinker-wc package compromised
- September 8, 2025: Only chalk organization (10 packages) republished clean .2 versions
- September 9, 2025 01:11 UTC: DuckDB packages compromised (CVE-2025-59037) - 4 packages affected
- September 9, 2025: DuckDB packages fixed with new versions (1.3.4, 1.30.0)
- Ongoing: 16 non-chalk packages remain without clean replacement versions
affected packages
Confirmed Compromised Packages (chalk organization)
Package | Compromised Version | Clean Version | Weekly Downloads |
---|---|---|---|
chalk | 5.6.1 | ✅ 5.6.2 | ~300M |
ansi-styles | 6.2.2 | ✅ 6.2.3 | ~371M |
strip-ansi | 7.1.1 | ✅ 7.1.2 | ~261M |
ansi-regex | 6.2.1 | ✅ 6.2.2 | ~244M |
wrap-ansi | 9.0.1 | ✅ 9.0.2 | ~198M |
supports-color | 10.2.1 | ✅ 10.2.2 | ~287M |
slice-ansi | 7.1.1 | ✅ 7.1.2 | ~60M |
has-ansi | 6.0.1 | ✅ 6.0.2 | ~12M |
supports-hyperlinks | 4.1.1 | ✅ 4.1.2 | ~19M |
chalk-template | 1.1.1 | ✅ 1.1.2 | ~4M |
DuckDB Packages Compromised (September 9, 2025 - CVE-2025-59037)
Package | Compromised Version | Fixed Version | Weekly Downloads |
---|---|---|---|
duckdb | 1.3.3 | ✅ 1.3.4 | ~149K |
@duckdb/node-api | 1.3.3 | ✅ 1.3.4-alpha.27 | ~83K |
@duckdb/node-bindings | 1.3.3 | ✅ 1.3.4-alpha.27 | ~72K |
@duckdb/duckdb-wasm | 1.29.2 | ✅ 1.30.0 | ~65K |
Other Compromised Packages WITHOUT Clean Versions Available
Package | Compromised Version | Maintainer | Weekly Downloads |
---|---|---|---|
debug | 4.4.2 | debug-js org | ~358M |
color-convert | 3.1.1 | Qix- | ~194M |
color-name | 2.0.1 | Qix- | ~192M |
is-arrayish | 0.3.3 | jonschlinkert | ~74M |
error-ex | 1.3.3 | Qix- | ~47M |
color-string | 2.1.1 | Qix- | ~27M |
simple-swizzle | 0.2.3 | Qix- | ~26M |
color | 5.0.1 | unknown | ~5M |
proto-tinker-wc | 0.1.87 | unknown | ~1K |
@coveops/abi | 2.0.1 | unknown | ~500 |
backslash | 0.2.1 | unknown | ~0.3M |
⚠️ CRITICAL: These 11 packages (~925M weekly downloads) were compromised but have NO clean replacement versions published. You must downgrade to earlier versions or remove these packages.
attack method
Attackers registered support@npmjs.help
to mimic legitimate npm support. Initial reports indicated phishing of Sindre Sorhus (chalk maintainer), but the compromise of 26 packages across multiple maintainers (including DuckDB on September 9) suggests either:
- Multiple maintainer accounts were compromised through ongoing phishing
- Shared maintainer access was exploited
- Coordinated campaign targeting high-value packages
The fact that only chalk packages and DuckDB received fixes while 16 packages from other maintainers remain unfixed indicates the attack scope exceeded initial assessments. The DuckDB compromise (CVE-2025-59037) occurred a day after the initial attack was discovered, showing the campaign’s persistence.
malware behavior
The injected code (full malicious snippet) operates only in browser environments. The heavily obfuscated JavaScript:
- Intercepts wallet APIs: Hooks into Ethereum wallet provider methods
- Modifies transactions: Silently changes recipient addresses before execution
- Multi-chain targeting: Supports ETH, BTC, SOL, LTC, BCH
- Obfuscation techniques: Encoded arrays, symbol-based replacements, nested conditionals
- Address replacement: Swaps legitimate addresses with attacker-controlled ones mid-transaction
The code specifically targets cryptocurrency transactions, making it invisible to users until funds are stolen.
detection patterns
Search for these obfuscated signatures in your codebase:
// Function names and strings
"_0x112fa8"
"stealthProxyControl"
"runmask"
"newdlocal"
// ERC20 approve function selector
"0x095ea7b3"
Primary attacker ETH address: 0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976
automated scanner
npm-vuln-scanner
A purpose-built Python scanner detects these compromised packages with intelligent severity analysis:
# Install from PyPI
pip install npm-vuln-scanner
# Quick scan with pipx (recommended)
pipx run npm-vuln-scanner scan
# Or use uvx
uvx npm-vuln-scanner scan
Key features (v1.2.0):
- 26 packages detected: including DuckDB packages (CVE-2025-59037) and all npmjs.help campaign packages
- zero dependencies: uses only Python standard library
- severity levels: critical (confirmed compromised), high (could install compromised), medium (safe version), warning (version unknown)
- semver analysis: understands
^
,~
,>=
version ranges - parallel scanning: efficient multi-threaded directory traversal
- comprehensive detection: package.json, node_modules, lock files
- json output mode: structured JSON for CI/CD integration with
--json
flag - export formats: JSON and CSV output for automation
Example output:
🚨 CRITICAL: 2 confirmed compromised version(s) found!
⚠️ HIGH: 1 package(s) could install compromised versions
debug
Version: 4.4.2
CONFIRMED: Compromised version installed
chalk
Version spec: ^5.6.0
Caret range can resolve to 5.6.1
Source: github.com/mjbommar/check-npm-debug-chalk | PyPI
manual detection
Check your project for compromised packages:
# Search for malicious patterns
rg -u --max-columns=80 "_0x112fa8"
rg -u "stealthProxyControl|runmask|newdlocal"
# Check installed versions
npm ls chalk debug ansi-styles color-convert strip-ansi wrap-ansi
# Verify package.json and lock files
grep -E "chalk|debug|ansi-styles|color-convert" package*.json
Review package-lock.json
or yarn.lock
for specific version numbers. Monitor wallet transactions for unauthorized activity.
remediation
Update affected packages immediately:
npm update chalk debug ansi-styles color-convert strip-ansi ansi-regex wrap-ansi supports-color duckdb
Verify clean versions:
- 10 chalk packages: Update to .2 versions (e.g., chalk 5.6.2, ansi-styles 6.2.3)
- 4 DuckDB packages: Update to fixed versions (duckdb 1.3.4, @duckdb/duckdb-wasm 1.30.0)
- 11 packages WITHOUT fixes - must downgrade:
- debug:
npm install debug@4.4.1
or earlier - color-convert:
npm install color-convert@3.1.0
or earlier - color-name:
npm install color-name@2.0.0
or earlier - color:
npm install color@4.2.3
or earlier - is-arrayish:
npm install is-arrayish@0.3.2
or earlier - error-ex:
npm install error-ex@1.3.2
or earlier - color-string:
npm install color-string@2.1.0
or earlier - simple-swizzle:
npm install simple-swizzle@0.2.2
or earlier - proto-tinker-wc:
npm install proto-tinker-wc@0.1.86
or earlier - @coveops/abi:
npm install @coveops/abi@2.0.0
or earlier - backslash:
npm install backslash@0.2.0
or earlier
- debug:
Clear cache and regenerate lock files:
npm cache clean --force
rm package-lock.json
npm install
Review recent cryptocurrency transactions. Rotate exposed credentials. Enable 2FA on npm accounts.
scanner implementation details
The npm-vuln-scanner
tool implements sophisticated detection through:
semver range analysis
The scanner understands npm’s semver ranges to detect potential vulnerabilities:
# Caret ranges (^5.6.0 can resolve to 5.6.1 or 5.6.2)
"^5.6.0" → HIGH severity (could install 5.6.1)
# Exact versions
"4.4.2" → CRITICAL severity (exact match)
# Tilde ranges (~7.1.0 allows patch updates)
"~7.1.0" → HIGH severity (could install 7.1.1 or 7.1.2)
# Safe versions
"5.5.0" → MEDIUM severity (package present but safe)
multi-source detection
The scanner checks:
- package.json: direct, dev, and optional dependencies
- node_modules: actual installed versions with package.json parsing
- package-lock.json: lockfile v1, v2, and v3 formats
- yarn.lock: yarn-specific lockfile parsing
parallel processing
Uses Python’s ThreadPoolExecutor
for concurrent:
- Directory traversal (finds all package.json files)
- node_modules scanning (checks installed versions)
- Lock file parsing (processes multiple formats)
usage patterns
# Scan multiple projects
npm-vuln-scanner scan /project1 /project2 /project3
# Include home and global npm
npm-vuln-scanner scan --include-home --include-global
# Export for CI/CD integration
npm-vuln-scanner scan --export-json report.json
# Check specific package.json
npm-vuln-scanner check ./frontend/package.json
# List all monitored packages
npm-vuln-scanner list
json output mode (v1.2.0)
Structured JSON output for CI/CD pipelines and automation:
# All commands support --json flag
npm-vuln-scanner --json scan
npm-vuln-scanner --json check package.json
npm-vuln-scanner --json list
# Parse with jq
npm-vuln-scanner --json scan | jq '.summary.by_severity.critical'
npm-vuln-scanner --json scan | jq '.findings.high[] | select(.package_name == "chalk")'
npm-vuln-scanner --json scan | jq -r '.findings.critical[] | "\(.package_name)@\(.version_spec)"'
JSON structure includes:
- scan_metadata: version, timestamp, paths, duration
- summary: severity counts, exit code, packages checked
- findings: organized by severity level
- recommendations: risk-appropriate actions (correctly advises “DO NOT run npm install until specs are fixed”)
Example output:
{
"scan_metadata": {
"scanner_version": "1.2.0",
"scan_timestamp": "2025-09-09T...",
"duration_seconds": 1.31
},
"summary": {
"by_severity": {
"critical": 0,
"high": 2,
"medium": 257
},
"exit_code": 1
},
"findings": {
"high": [
{
"package_name": "chalk",
"version_spec": "^5.6.0",
"message": "Caret range can resolve to 5.6.1"
}
]
}
}
exit codes
0
: no compromised packages found1
: compromised packages detected or error130
: scan interrupted by user (Ctrl+C)
context
This attack reveals critical npm ecosystem vulnerabilities:
- Incomplete remediation: 16 of 26 compromised packages (~925M weekly downloads) remain without clean versions
- Multiple maintainer compromise: Attack crossed organizational boundaries, compromising DuckDB on September 9 (CVE-2025-59037), suggesting coordinated campaign
- Response gaps: Only chalk organization and DuckDB responded quickly; other maintainers haven’t published fixes
- Ecosystem dependencies: Packages like
debug
,color-convert
, and DuckDB are deep dependencies for thousands of projects - Continued attacks: The campaign continued into September 9 with DuckDB packages, showing persistence beyond initial discovery
The 2-hour initial exposure window combined with ~2.5B weekly downloads means millions of systems potentially installed compromised versions. The lack of fixes for 16 packages creates ongoing risk - users must manually downgrade or remain vulnerable.