On September 8, 2025, at approximately 13:00 UTC, an attacker gained access to the npm account of Josh Junon (qix) and began publishing backdoored versions of his packages. The compromise was quickly spotted by community members who alerted Junon directly.

Junon confirmed the breach originated from a phishing email claiming to be a two-factor authentication (2FA) reset notice. The message came from the domain npmsj.help, registered just three days prior to the attack.

Scope of the compromise

Security researcher Kevin Beaumont published a list of affected packages on Mastodon, highlighting the scale of the incident. The color package alone, which Junon maintains, sees roughly 32 million weekly downloads. Other compromised packages in the ecosystem include chalk and simple-swizzle.

At the time of Beaumont's update, Junon remained locked out of his npm account. While chalk was patched by co-maintainer Sindre Sorhus, simple-swizzle was still serving malicious versions. The npm team had contacted Junon and stated they were working to remove the rogue packages, though response times were slow given the severity of the situation—Junon reported waiting nearly two hours without a reply from npm support.

What the payload does

The obfuscated payload, available for analysis via pastebin, appears designed for browser environments rather than servers or developer machines. That means several conditions would need to align for an attack to succeed: the malicious dependency would have to be included in a front-end build, the site would need to be deployed, and a user would have to authorize a transaction while the drainer was active.

De-obfuscation and analysis efforts, including a loose TypeScript port of the payload, revealed the following behavior:

  • fetch and XMLHTTPRequest are hooked to scan response bodies for cryptocurrency addresses (Bitcoin, Solana, Litecoin v2, and similar). Found addresses are swapped for attacker-controlled ones. Only response bodies are modified—request bodies are left untouched—suggesting the code targets API responses that specify recipient addresses.
  • Every 500ms, up to 50 times, the payload calls window.ethereum.request to check for authorized Ethereum accounts in MetaMask. If any are found, window.ethereum is monkey-patched to redirect transactions.

Specific transaction types targeted by the Ethereum patch include:

  • approve(address,uint256) (0x095ea7b3): replaces the destination and sets the amount to maximum. This code path also logs known DEX names, including Uniswap, PancakeSwap, 1inch, and SushiSwap.
  • permit(address,address,uint256,uint256,uint8,bytes32,bytes32) (0xd505accf): replaces the destination and maxes out the value.
  • transfer(address,uint256) (0xa9059cbb): replaces the destination but preserves the original amount.
  • transferFrom(address,address,uint256) (0x23b872dd): replaces the destination but preserves the original amount.

A separate Solana code path exists that changes various fields to 19111111111111111111111111111111, though it remains unclear whether that manipulation would actually succeed.

Situation at time of writing

Analysis of the full extent of the attack was still ongoing. Given the payload's browser-centric design, simply accepting a pull request that bumps dependencies or running CI tests does not necessarily trigger malicious behavior—but the complete picture of affected packages and the payload's full intent had yet to be determined.