On 4 August, at 09:00 UTC, somebody started pushing malicious code into the keyv repository… If you don't know what Keyv is, that's kind of the point. It's a little key-value storage library. You put something in, you get something out, maybe it expires after a while. Nothing particularly exciting. Nothing that sounds like the sort of thing an attacker would spend a Tuesday morning worrying about.
This is Rule 5 of the Flat Stack Manifesto: Every dependency is a decision. More than 400 npm packages that include Keyv and Cacheable ecosystem eventually shipped the malicious code. This means folks might have been installing some entirely different package, or updating something, and that intentional hack slips in…
my-awesome-app
└── some-framework
└── some-cache-wrapper
└── keyvYou’re building your awesome app… you want to have a feature from a framework, that framework uses some other dependency which.. in turn… is compromised and BAM… you’re running malicious code.
In this case, the hacker was including code from the Shai-Hulud family. It’s a hunter of secrets… and man there are a lot it could find: cloud credentials, GitHub and npm credentials, CI/CD secrets, developer credentials, AI configuration for Claude, OpenAI, Cursor and Gemini, cryptocurrency wallets and even /etc/shadow.
It also sneaks itself into Claude Code hooks and VS Code configuration. Its command-and-control infrastructure is resolved through an Ethereum smart contract, which means the people operating it can change where the malware phones home without having to ship another version of the infected package.
You’re not constantly installing keyv.. in fact you probably never even knew it was on your stack… You install a thing because you need a thing, and that thing installs six other things because they need things, and pretty soon you've got a small city living in your application.
Most of those packages are perfectly legitimate. Most are maintained by people doing their best. Most of them are probably never going to cause you any trouble.
But every one of them is somebody else's code running in your environment.
“But what could a cache possibly do?”
What's the worst it can do? Whatever the process running the cache can do. It’s not like the cache got a little sandbox labeled CACHE THINGS ONLY, if your Node process can read environment variables, the cache can read them.
If your build process has an AWS credential that the cache has to read… the cache can potentially steal it. If your developer account has a GitHub token, SSH keys, npm credentials or AI API keys sitting around, code running in that developer's environment may be able to find them.
It's not, “The cache can mess up my cache,” it’s “The cache can potentially become another program running as me.”
That's a much bigger problem than malware in your application, it’s like inviting the vampires into the house… I mean, sure, you thought it was the pizza guy, but once they’re in… well… this isn’t a horror movie, this is real life and all the creds sitting in reach of that process you didn’t even know was there can be sucked dry…
Of course, it’s worse than just the “vampire in the house” analogy (metaphor? I always get that wrong…) The Shai-Hulud hack wasn't just stealing secrets from the machine where it landed, it used those stolen credentials to go after other repositories and packages.
That's how one compromised maintainer account can become hundreds of compromised packages. So you end up wth a chain that looks something like this:
bad code → developer machine → credentials → GitHub/npm → more packages → more developers → more credentials
Claude likes to talk about blast radius… I look at this cycle and think about the dinosaur killing asteroid… A little rock falling from the sky… see it wasn’t the rock itself that killed the dinosaurs, it was all the dirt and heat it kicked up into the sky. It’s not that the cache dependency is big and scary, it’s what it can do once it hits…
Now keep in mind, the compromised keyv code ended up in four hundred OTHER packages. It wasn’t that 400 devs and admins included it on purpose, they probably didn’t even know that THEY were using keyv. And they weren't hacked… they just passed it along.
Somebody chose a dependency that needed a dependency that needed a dependency that eventually led to Keyv. It's not inherently wrong. Dependency management is how modern software gets built. But we have gotten awfully good at pretending that dependencies are free.
They're not.
Every dependency is another piece of software that gets to execute in your environment. Sometimes that's absolutely worth it. Your database driver? Probably worth it (but do you really NEED that database?) Your cloud SDK? Probably worth it. (but do you really need your app to have all that access?)
But the tiny utility you imported because writing the equivalent functionality yourself would take twenty minutes? I mean, I’ve seen people install huge packages with dozens of dependencies that aren’t used where they could have written six lines of code.
I actually asked an AI “How many lines of code do you think a function in node would need to be to replace the functionality of keyv?” and it wrote the code:
const cache = new Map();
function set(key, value, ttl = 0) {
cache.set(key, {
value,
expires: ttl ? Date.now() + ttl : 0
});
}
function get(key) {
const item = cache.get(key);
if (!item) return undefined;
if (item.expires && Date.now() > item.expires) {
cache.delete(key);
return undefined;
}
return item.value;
}
function del(key) {
cache.delete(key);
}Less than 20 lines of code… Nobody upstream can quietly change them. There isn't another maintainer account involved. There isn't another dependency tree.
Or maybe you don't need a cache at all. Sometimes you can get rid of an entire category of software you have to trust. Precomputing isn't just a performance optimization. Sometimes it's dependency reduction.
This doesn't mean writing everything yourself, nobody wants to maintain their own TLS implementation because they have concerns about dependencies. There are enormous pieces of modern software that we should absolutely reuse.
The point is to distinguish between:
“I need this software.”
and:
“This software saves me twenty minutes, so I'll let it execute with the same privileges as everything else.”
Those are NOT the same decision. A dependency should have to earn its place, not because every package is dangerous but because every package is executable code that somebody else controls.
And don’t forget that there’s a new category of credential sitting around on developer machines now -- AI credentials (yes I used an emdash because people think only AI uses them). The details vary, but the basic problem is familiar: tokens, configuration and instructions live somewhere on the developer's machine because the tools need them.
An attacker doesn't necessarily need some brilliant zero-day to find them.
An API key isn't necessarily the whole story anymore. An agent configuration can describe what the agent is allowed to access and how it is supposed to operate.
So the thing you thought of as “my coding assistant configuration” is increasingly part credential, part tool configuration, part map of your development environment.
There isn't a magic dependency scanner that solves this. And there certainly isn't a practical world in which everybody audits the source code of every package in their dependency tree… the trees are too big.
The useful discipline is simpler.
Be suspicious of things you don't actually need.
If a dependency buys you enormous value, use it. If it is fundamental infrastructure, accept that you're making a serious trust decision and secure the environment accordingly.
If it saves you fifteen minutes of typing, maybe those fifteen minutes are cheaper than giving another piece of software a permanent place inside your house.
You're not just installing a library, you're giving somebody else's code an opportunity to run with your identity, and your identity may have access to a lot more than the library's documentation suggests. (the number of times I’ve been given access to things because I run the project… when I’m not even coding! Urf…)
It just had to be inside the house when you handed it the keys.
That's the extinction level blast radius.