
Analysis of the NPM Supply Chain Attack
A technical breakdown of the September 2025 malware campaign that targeted 2 billion downloads by compromising 'chalk' and 'debug' packages.
View Malware Samples⚠️ WARNING: MALICIOUS CODE AHEAD The code analyzed below is from a live malware campaign. DO NOT EXECUTE unless in a secure, sandboxed environment.
Executive Summary
On September 8, 2025, a massive supply chain attack compromised the account of a prolific maintainer, qix. This led to the publication of malicious versions of 18 popular packages, including chalk and debug, which are collectively downloaded over 2 billion times per week.
The payload was a sophisticated, client-side malware designed to steal cryptocurrency from the users of websites that bundled these compromised packages.
This analysis is a companion to my detailed article: How One Phishing Email Injected Crypto-Stealing Malware into Billions of NPM Downloads.
Attack Vector
The attack was initiated through a well-executed social engineering campaign:
- The Phish: The maintainer received an email from
support@npmjs.helpclaiming their 2FA needed updating. - Credential Hijacking: A cloned login page captured the username, password, and the Time-based OTP (2FA) in real-time.
- Deployment: Using the hijacked session, the attackers published patch versions containing a single, obfuscated line of JavaScript injected into the package's core file.
Malware Analysis
The malware utilized a two-stage activation process to remain stealthy.
Stage 1: Environment Check
The script first checks if it is running in a browser with a Web3 wallet installed (window.ethereum). If not, it falls back to a passive mode to avoid detection by automated scanners.
// The malware only activates its full potential if a Web3 wallet is detected.
if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
detectAndInitializeWalletHooks();
} else {
// If no wallet is found, it falls back to only the network interception module.
initializeAddressSwapper();
}
Stage 2: The Two-Pronged Attack
Module A: Passive Network Interception
This module hijacks the browser's native fetch and XMLHttpRequest functions.
- It intercepts network response bodies.
- It uses Regex to find strings matching crypto addresses (BTC, ETH, SOL).
- It uses a Levenshtein distance algorithm to swap the address with a visually similar one from a hardcoded list of 280+ attacker wallets.
Module B: Active Transaction Hijacking
This module hooks into window.ethereum.request. When a user tries to sign a transaction:
- It inspects the transaction payload.
- For
transferorapprovecalls, it surgically replaces thetoaddress with the attacker's wallet. - The user sees a Metamask popup that looks legitimate, but signs a transaction sending funds to the attacker.
Remediation
If your project depends on chalk or debug:
- Verify: Run
grep -r 'checkethereumw' node_modules/to check for the malicious footprint. - Reinstall: Delete
node_modulesandpackage-lock.json, then runnpm install. - Rotate: Assume your CI/CD environment secrets are compromised.
Indicators of Compromise (IoCs)
| Type | Value |
|---|---|
| Phishing Domain | npmjs.help |
| C2 Domain | static-mw-host.b-cdn.net |
| Attacker IP | 185.7.81.108 |
| Attacker ETH | 0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976 |
A full list of the 280+ hardcoded wallets is available in the deobfuscated source code.