All vulnerabilities
CRITICALSupply chainexploited in the wildcurated

NPM-QIX-CHALK-DEBUG-2025

npm · chalk, debug, ansi-styles, strip-ansi, color-convert (+13 more)

Summary

On 8 September 2025, the largest npm supply-chain attack ever by sheer reach hit foundational packages, chalk, debug, ansi-styles, strip-ansi, and 14 more, that together are downloaded over 2 billion times a week. The cause was a single phishing email. A respected maintainer was tricked by a fake "your npm 2FA is expiring" message into handing over his account, and the attackers published poisoned versions of his ultra-popular libraries. The payload was a crypto clipper: browser code that silently swapped any cryptocurrency address a user was sending to with the attacker's. Automated scanners flagged the poisoned versions within minutes and they were pulled within about two hours, and the actual theft came to roughly a thousand dollars, the one piece of good news in an attack that sat, briefly, under nearly the entire JavaScript ecosystem.

How it happened

It began with phishing. The maintainer Josh Junon, known as "Qix," received a convincing fake email from the spoofed domain support@npmjs.help (registered just three days before the attack), warning that his npm two-factor authentication was expiring and that he needed to re-authenticate. He did, and handed the attackers his account. They immediately published malicious versions of 18 of his foundational packages (chalk@5.6.1, debug@4.4.2, ansi-styles@6.2.2, and more) which collectively account for over 2 billion weekly downloads, making this the largest npm supply-chain attack by volume in history.

The payload was a browser-based crypto clipper. It hooked the browser's fetch and XMLHttpRequest, used fuzzy (Levenshtein-distance) matching to swap a victim's intended cryptocurrency address with a look-alike attacker address across Ethereum, Bitcoin, Solana, Tron, Litecoin, and Bitcoin Cash, hijacked MetaMask transactions through window.ethereum, and even rewrote transaction parameters on DeFi front-ends like Uniswap and PancakeSwap before the user signed. The effect: a user sending crypto on any affected web app would have their destination address quietly rewritten to the attacker's, in transit, without noticing.

The damage

Despite the staggering reach, the actual theft was tiny: on-chain trackers put it at roughly $1,000 total, with only about $500 left in the attacker's wallet and the ETH actually clipped amounting to about five cents. That tiny number came down to several things: automated malware scanners caught the poisoned versions within minutes (the packages were live about two hours before cleanup, not two hours before anyone noticed), the clipper only affected browser-based crypto flows so servers and CI pulling the bad versions were not drained, and the crypto industry reacted fast, Ledger's CTO publicly urged users to halt on-chain transactions. It was a near-miss of historic proportions: the same access could have shipped a far more destructive payload, a credential stealer or a wiper, to nearly the entire JavaScript ecosystem. It became the canonical "one phished maintainer, two billion downloads" story.

Why qix-chalk still matters

It lays bare the asymmetry of npm: a single maintainer's account sits in front of billions of installs, and the only thing guarding it is often a phishable second factor. The small payload and quick detection were luck, not design. The lessons are sharp: use phishing-resistant 2FA (FIDO2 or passkeys), which is un-phishable and would have defeated the fake "2FA reset" trick that ordinary one-time codes could not; train maintainers to distrust "your account is expiring, re-authenticate now" emails and to check the real domain rather than the link; pin dependencies and avoid auto-adopting brand-new releases of core libraries; and watch for unexpected versions. It is a same-year sibling of the self-replicating Shai-Hulud worm.

How to fix it

  • Pin to or roll back to known-good versions of the affected packages and rebuild from a clean lockfile.
  • For web apps that served a poisoned version, treat it as a client-side compromise: warn users and audit any crypto transactions made during the window.
  • The maintainer, and anyone hit by the same lure, must reset credentials and move to phishing-resistant 2FA.

How to avoid it

  • Use phishing-resistant 2FA (FIDO2 or passkeys) on package-publishing accounts; the fake "2FA reset" lure that worked here cannot defeat a hardware key.
  • Train maintainers to distrust "your account is expiring / re-authenticate now" emails and to verify the real domain, not the link.
  • Pin dependencies and do not auto-adopt brand-new releases of core libraries; a two-hour-poisoned version should never flow straight into your build.
  • Monitor for unexpected new versions of critical packages and pull from a vetted internal mirror.

References

Related vulnerabilities

All Supply chain →