color npm package compromised
Thanks to my sponsors: Björn Marschollek, Sam Leonard, ShikChen, Marty Penner, anichno, callym, Mathias Brossard, ZacJW, David White, Christopher Valerio, Torben Clasen, Xirvik Servers, Radu Matei, David Barsky, Yves, Jimmy Hartzell, Matthias Zepper, Mattia Valzelli, Lyssieth, Pete Bevin and 277 more
On September 8 2025, around 13:00 UTC, someone compromised Josh Junon’s npm account (qix) and started publishing backdoored versions of his package.
Someone noticed and let Josh know:
Josh confirmed he’d gotten pwned by a fake 2FA (two-factor authentication) reset e-mail:
The phishing e-mail came from npmsj.help
(registered 3 days prior) and claimed
users had to reset their 2FA:
The 2FA phishing e-mail
The raw email is available for the curious — it looks like it was sent via mailtrap (I’ve sent them an e-mail about it).
Over on Mastodon, Kevin Beaumont provided a list of affected packages:
And pointed out the scale of the attack: color
alone has ~32 million weekly
downloads:
The payload
The complete payload is available on pastebin.
According to initial analysis, it appears it’s not meant to be running in a server environment, or on developers’ machines (in other words, not in nodejs/bun/etc.), but in the browser.
Which would mean that for the attack to be successful:
- Someone maintaining a crypto website/web-powered app would have to upgrade to the backdoored dependencies
- Those dependencies would have to be used on the front-end
- The crypto website would have had to be built, packaged, deployed
- Users of the website would’ve had to make transactions with the drainer active
In other terms, I think that if all people did was accept a PR that bumped some dependencies, and some tests ran in CI, then nothing bad has happened, yet. But people are still figuring out exactly what the payload is supposed to do, and all the affected packages.
De-obfuscating the payload through https://obf-io.deobfuscate.io/ yields good results, see this gist.
I went a step further and did a loose port to TypeScript to understand more of what’s going on.
In short, fetch
and XMLHTTPRequest
are hooked so that any crypto addresses
found in the response body that look like Bitcoin, Solana, Litecoin v2 etc. are
modified to be one of the many addresses controlled by the attacker.
Note that only the response body is modified, not the request body. Presumably… this targets API calls that would request which address to send funds to, and do the transfer through some other means?
Additionally, every 500ms up to 50 times, window.ethereum.request
is called to see if
any Ethereum accounts have been authorized for use with Metamask.
If so, window.ethereum
is monkey-patched to alter various transactions to go
attacker-controlled addresses.
In particular, it looks for:
approve(address,uint256)
(0x095ea7b3) — replacing the destination & maxing out the amount- this codepath also logs the DEX name if known: Uniswap, PancakeSwap, 1inch, SushiSwap
permit(address,address,uint256,uint256,uint8,bytes32,bytes32)
(0xd505accf) — replacing the destination & maxing out the valuetransfer(address,uint256)
(0xa9059cbb) — replacing the destination but keeping the amounttransferFrom(address,address,uint256)
(0x23b872dd) — replacing the destination but keeping the amount
There’s a Solana codepath as well, which changes various fields to 19111111111111111111111111111111
, but
it’s unclear to me whether that would do anything successfully.
Current situation
Sep 8, 17:19 UTC
NPM has contacted Josh and told him they are working to the remove the packages.
See Josh’s timestamped comment
Sep 8, 17:11 UTC
- John is still locked out of his npm account
- chalk was patched by Sindre
- simple-swizzle is still compromised
see the obfuscated code starting with const _0x112fa8
The npm team seems rather unresponsive given the urgency of the situation:
It’s been almost two hours without a single email back from npm. I am sitting here struggling to figure out what to do to fix any of this. The packages that have Sindre as a co-publisher have been published over but even he isn’t able to yank the malicious versions AFAIU. If there’s any ideas on what I should be doing, I’m all ears.
The best place to stay informed is probably Kevin’s thread on Mastodon.
Here's another article just for you:
Rust modules vs files
A while back, I asked on Twitter what people found confusing in Rust, and one of the top topics was “how the module system maps to files”.
I remember struggling with that a lot when I first started Rust, so I’ll try to explain it in a way that makes sense to me.
Important note
All that follows is written for Rust 2021 edition. I have no interest in learning (or teaching) the ins and outs of the previous version, especially because it was a lot more confusing to me.