All projects

CompletedSep 2025

npm Supply-Chain Attack: Inside the chalk and debug Crypto-Stealer

Analysis of the September 2025 npm compromise, where one phishing email led to malicious versions of chalk, debug and 16 other packages. I deobfuscated the payload and documented how it swaps crypto addresses and rewrites wallet transactions inside the browser.

What happened

On 8 September 2025, the maintainer of some of the most downloaded packages on npm received a convincing email from npmjs.help claiming their two-factor authentication needed to be updated. The link led to a copy of the npm login page that captured the username, password and 2FA code.

With the account in hand, the attackers published new patch versions of 18 packages, including chalk and debug. Each one carried a single, heavily obfuscated line of JavaScript. The community and automated scanners caught it quickly, but these packages are pulled in by a huge share of JavaScript projects.

I wrote up the incident on Medium in How One Phishing Email Injected Crypto-Stealing Malware into Billions of npm Downloads, and published the deobfuscated payload with my annotations in the repository.

How the payload works

The code only does anything in a browser. It checks for window.ethereum, the object that wallets such as MetaMask inject into web pages, and picks its attack from there.

Address swapping. It wraps the browser's fetch and XMLHttpRequest, scans every response for cryptocurrency addresses (BTC, ETH, SOL, Tron and more), and replaces them with one of more than 280 attacker addresses. The clever part is how it chooses: it uses Levenshtein distance to pick the attacker address that looks most like the original, so a quick visual check doesn't catch it.

Transaction rewriting. If a wallet is present, it wraps the wallet's request, send and sendAsync methods. For a plain transfer it changes the recipient. For token contracts it looks for the transfer, transferFrom and approve function selectors in the call data and swaps the recipient or spender address inside it. The wallet then asks the user to sign what looks like their own transaction.

What I published

  • deobfuscated_malware.js: the payload after automated deobfuscation, kept as evidence.
  • annotated_malware.js: the same code with meaningful names and comments explaining each step.
  • Remediation steps for developers, and the indicators: the phishing domain, the asset and C2 domains, and the main attacker wallets.

Why it matters

This attack didn't need a vulnerability in any code. It needed one maintainer, one convincing email and one 2FA code typed into the wrong page, and phishing-resistant authentication such as security keys would have stopped it. For anyone shipping JavaScript, it's also a reminder to pin dependency versions and treat a sudden patch release of a stable package with suspicion.