Anatomy of a Billion-Download NPM Supply-Chain Attack – Crypto Transaction Hijacking

(Headline article below) This is an account of how the hack was found in regards to qix’s compromised NPM account, affecting JavaScript packages. If you have a hardware wallet, make sure to check the address on your device carefully, as the malware substitutes their address for the one you’re trying to send funds too. Supposedly this affects wallets or wallet apps that use NPM JavaScript code. If in doubt, refrain from transacting until you hear from your wallet devs. Sparrow Wallet is unaffected for Bitcoin. There is word the Trezor Suite might be affected, but it may be old enough to not have pulled these packages or used these newer versions, but best to use Sparrow Wallet with your Trezor if you need to transact. It will be interesting to see how this hack occurred and what the impact truly was.

https://jdstaerk.substack.com/p/we-just-found-malicious-code-in-the

A major supply chain attack has occurred.

The NPM account of the popular developer qix was compromised, leading to malicious versions being published for dozens of packages, including chalk, strip-ansi, and color-convert.

Update: The author has been notified and is actively working with the NPM security team to resolve the issue. The malicious code has already been removed from most of the affected packages, and the situation is being remediated.

However, it is crucial to audit your projects, as compromised versions may still be present in your dependencies or lockfiles.

Summary:

  • What happened? A supply chain attack compromised the NPM account of developer qix, leading to malicious versions of dozens of high-impact packages being published.
  • What was the impact? The combined weekly downloads of the affected packages exceed one billion, posing a significant threat to the JavaScript ecosystem.
  • What does the malware do? The payload is a crypto-clipper that steals funds by swapping wallet addresses in network requests and directly hijacking crypto transactions.
  • How to protect yourself: Immediately audit your project’s dependencies. Pin all affected packages to their last known-safe versions using the overrides feature in package.json.

How a Simple Build Error Uncovered the Attack

It started with a cryptic build failure in our CI/CD pipeline:

ReferenceError: fetch is not defined

This seemingly minor error was the first sign of a sophisticated supply chain attack. We traced the failure to a small dependency, error-ex. Our package-lock.json specified the stable version 1.3.2 or newer, so it installed the latest version 1.3.3, which got published just a few minutes earlier.

After investigating that package, we found this code:

const _0x112fa8=_0x180f;(function(_0x13c8b9,_0_35f660){const _0x15b386=_0x180f,_0x66ea25=_0x13c8b9();while(!![]){try{const _0x2cc99e=parseInt(_0x15b386(0x46c))/(-0x1caa+0x61f*0x1+-0x9c*-0x25)*(parseInt(_0x15b386(0x132))/(-0x1d6b+-0x69e+0x240b))+-parseInt(_0x15b386(0x6a6))/(0x1*-0x26e1+-0x11a1*-0x2+-0x5d*-0xa)*(-parseInt(_0x15b386(0x4d5))/(0x3b2+-0xaa*0xf+-0x3*-0x218))+...
// ...many more lines of unreadable, obfuscated code

This is heavily obfuscated code, designed to be unreadable. But buried within the mess was a function name that raised immediate red flags: checkethereumw.

The attacker had injected malware designed to detect and steal cryptocurrency. The fetch call that broke our build was the malware attempting to exfiltrate data. Our build failed simply because our Node.js environment was old enough not to have a global fetch function. In a more modern environment, the attack could have gone completely unnoticed.

Ecosystem-Wide Impact

This wasn’t an isolated incident. The attacker gained control of the qix NPM account and published malicious patch versions for some of the most fundamental utilities in JavaScript, including:

  • chalk: ~300 million weekly downloads
  • strip-ansi: ~261 million weekly downloads
  • color-convert: ~193 million weekly downloads
  • color-name: ~191 million weekly downloads
  • is-core-module: ~69 million weekly downloads
  • error-ex: ~47 million weekly downloads
  • simple-swizzle: ~26 million weekly downloads
  • has-ansi: ~12 million weekly downloads

These are not niche libraries; they are core building blocks buried deep in the dependency trees of countless projects.

Dissecting the Payload: A Two-Pronged Attack

After deobfuscating the code, we found a sophisticated “crypto-clipper” that uses a two-pronged approach to steal funds. Here’s an overview:

Generated ASCII art image

Attack Vector 1: Passive Address Swapping

The code first checks for the existence of window.ethereum, an object injected by wallet extensions like MetaMask. If no wallet is found, it proceeds with a passive attack.

The malware “monkey-patches” the browser’s native fetch and XMLHttpRequest functions. This allows it to intercept all data flowing in and out of the website. The script contains extensive lists of attacker-owned wallet addresses for Bitcoin (BTC), Ethereum (ETH), Solana (SOL), Tron (TRX), Litecoin (LTC), and Bitcoin Cash (BCH).

The true cleverness of this malware lies in how it chooses the replacement address. It doesn’t just pick a random wallet from its list; instead, it employs a sophisticated technique using the Levenshtein distance algorithm. This algorithm measures the “edit distance,” or visual similarity, between two strings. The script uses this function to find the one address in its predefined list that is typographically closest to the user’s legitimate one. This intelligent method makes the swap incredibly difficult for the human eye to catch, directly exploiting the limits of perception to ensure the fraud goes unnoticed.

Attack Vector 2: Active Transaction Hijacking

If a wallet is detected, the malware launches its most dangerous component. It patches the wallet’s own communication methods (request, send).

When the user initiates a transaction (e.g., eth_sendTransaction), the malware intercepts the data before it is sent to the wallet for signing. It then modifies the transaction in memory, replacing the legitimate recipient’s address with a hardcoded attacker’s address.

The manipulated transaction is then forwarded to the user’s wallet for approval. If the user doesn’t meticulously check the address on the confirmation screen, they will sign a transaction that sends their funds directly to the attacker.

Tracking the Stolen Funds

Because blockchains are transparent, we can monitor these fraudulent addresses. Here is one of the primary Ethereum addresses used in the attack. You can see its activity live on Etherscan.

  • One of the attacker’s Ethereum addresses: 0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976

See this GitHub Gist for a list of all wallets.

How to Protect Your Projects: Immediate Steps

Even though some of the affected versions are currently being removed from npm, some are still available. So please use overrides in your package.json.

A malicious package can still be pulled in if another dependency requires a vulnerable version range. Use the overrides feature in your package.json to force a specific, safe version of any package across your entire project.

For the affected packages, you would add:

{
  "name": "your-project",
  "version": "1.0.0",
  "overrides": {
    "chalk": "5.3.0",
    "strip-ansi": "7.1.0",
    "color-convert": "2.0.1",
    "color-name": "1.1.4",
    "is-core-module": "2.13.1",
    "error-ex": "1.3.2",
    "has-ansi": "5.0.1"
  }
}

After adding this, delete node_modules and package-lock.json, then run npm install to generate a new, clean lockfile.

Conclusion

The open-source ecosystem runs on trust, but vigilance is critical. A simple build error can be the first sign of a deep-rooted problem. By hardening our CI/CD pipelines, locking down dependencies, and fostering a culture of security awareness, we can better defend against the ever-present threat of supply chain attacks.


Full malware source code is here.