May 30, 2026
A worm shipped to npm with a valid signature, and I rotated tokens till 2am
I found out from a Slack bot. 11-something on a Tuesday night, I’m half-watching something and not really watching it, and our security…
The Expert Developer
4 min read
I found out from a Slack bot. 11-something on a Tuesday night, I'm half-watching something and not really watching it, and our security channel lights up: echarts-for-react flagged in the latest dependency scan.
New version. Post Install script doing things a charting wrapper has absolutely no business doing.
My stomach did the thing.
Because we use echarts-for-react. Of course we do. It's a chart. 3.8 million downloads a month, it's been in our package.json since before I started.
You don't think about it. That's the whole problem, isn't it? You don't think about the chart library.
okay so what actually happened
If you weren't paying attention to npm this month, May was rough. Like, genuinely one of the worse stretches I can remember for the registry.
It started on the 11th with TanStack. Somebody got into their GitHub Actions pipeline and pushed 84 malicious versions across 42
@tanstack/* packages.
The number that broke my brain: it all happened in about six minutes. Six. By the time a human could've noticed, it was done and replicating. And here's the part that actually scared me, the first documented case of a malicious npm package shipping with valid SLSA provenance.
The supply-chain attestation. The thing we were all told to look for as the green checkmark that means "this is legit." It was legit. It was signed. It was also a worm.
OpenAI got caught in that one, by the way. They put out a note saying two employee machines in their corporate environment got hit. If it can walk into OpenAI's laptops it can walk into yours, is kind of how I read that.
Then the 19th, my night, the @antv thing. They're calling it "Mini Shai-Hulud," which, yeah, the Dune worm name, because it burrows through your CI and spawns more of itself. One npm account got compromised and the attacker pushed 637 malicious versions across 317 packages in a 22-minute automated burst.
Not 22 minutes of a person typing. 22 minutes of a script doing what scripts do. size-sensor (4.2M downloads a month), echarts-for-react, @antv/scale, timeago.js. Stuff that's three layers deep in everybody's tree.
And then like three days after that, the 22nd, a separate campaign called TrapDoor showed up across npm and PyPI and crates.io, going after crypto and Solana and AI devs specifically. Different crew, same energy. It just kept coming.
The actual night
So what does this look like for a normal dev who is not a security researcher and just wants to ship a dashboard feature?
It looks like opening your lockfile at midnight and reading it like a hostage note.
I pinned everything first. npm ci is your friend here, the second you suspect something you stop letting anything float.
Then I went through package-lock.json checking resolved versions against the bad-version lists that Snyk and the GitHub advisory folks were pushing out in basically real time.
We weren't on the exact poisoned version of echarts-for-react, thank god, our renovate bot is lazy and hadn't bumped it yet. First time being behind on updates ever actively saved me. I laughed, a little hysterically.
But "we're probably fine" is not a thing you can send to your CTO at midnight. So you assume you're not fine. You rotate. npm tokens, the GitHub Actions secrets, the deploy keys, the one AWS key somebody definitely hardcoded in a workflow two years ago and swore they'd remove. Because that's what these worms eat.
They don't want your code. They want the credentials sitting in your CI environment so they can go publish from somebody else's account and keep the chain going. That's the whole trick. CI is where the secrets live and CI is where nobody's looking at 1am.
Finished around 2. Maybe 2:15. Drank a beer I'd been ignoring, which by then was warm. Slept badly.
the thing I keep chewing on
I used to feel a little smug about the npm-is-a-dumpster-fire takes. Like, come on, it's fine, you just keep your deps updated, you read the changelogs, you're a professional.
Yeah, I was wrong about that. Updating fast is exactly what burned people this time. The folks who auto-merged their dependency bumps got the malware delivered nice and fresh. The lazy ones got lucky. That's not a system working. That's a coin flip wearing a hard hat.
And the provenance thing genuinely shook me. We spent two years being told the answer to supply-chain attacks was signing and attestation, build it in the open, prove the artifact came from the source.
And it did come from the source. The source was compromised at the pipeline level, so the signature just… certified the poison. The checkmark was honest. It's just that the checkmark was never answering the question I thought it was answering.
So what do you actually do, other than panic. Honestly, the boring stuff. Pin your dependencies and use npm ci everywhere, no floating ranges in anything that touches prod.
Turn on --ignore-scripts if your build can survive it (a shocking amount of packages run postinstall for no real reason, and that's the front door). Scope down your CI tokens so a leaked one can't publish to your whole org.
Keep a lockfile and actually look at the diff when it changes, even when it's annoying, especially when it's annoying. None of this is clever. All of it is the stuff we skip because it's Tuesday and the feature's due.
I don't have a neat ending for this. I'm tired and slightly paranoid and I keep opening our dependency graph just to stare at it, all 1,400 transitive packages, every one of them a little door I'm trusting some stranger not to leave unlocked.
We built the whole web on that trust and most days it holds. This month it didn't, three times.
Anyway. Go check what version of echarts-for-react you're on. I'll wait.