When a stranger's repo configures your AI. The ChainDrop case
In August 2026 a worm started writing startup hooks into AI agent config files. The trigger isn't installing, it's starting your tool inside the project. Ten steps to check, respond and harden for good, including the question of what order to clean up in.
This Academy explains in a lot of places how to hand your AI context automatically at startup. A hook that fires on every new session is one of the most useful things in the whole setup. In August 2026 somebody used that same mechanism as a way to spread.
That isn't a reason to abandon hooks. It's a reason to treat them as what they are: code that runs without asking. This playbook covers what happened, how to check whether it affects you, and in what order to respond. The order is the interesting part, because that's where the experts disagree.
1. What happened
On 4 August 2026 the maintainer account behind two very widely used npm packages was taken over, keyv and cacheable. The injected code, called ChainDrop or Mini-Shai-Hulud depending on the source, propagated itself within hours: over 400 packages across more than 1700 versions. Elastic puts the affected reach at over 1.3 billion downloads per month, keyv alone at around 600 million.
Those numbers describe reach, not infections. Not every download is a compromised machine. But they tell you how many projects this thing potentially touched, and that is a large number.
The part that concerns you here isn't the npm part. The worm writes a startup configuration for your AI tooling into infected project folders. Specifically a startup hook in .claude/settings.json and a task in .vscode/tasks.json that runs when the folder is opened.
2. Why it reaches you without installing anything
This is the point that surprises most people.
When malicious code sits in a package's install step, the defence is obvious: don't install, nothing happens. With a startup hook in the project configuration it works differently. The trigger isn't the build, it's your tool starting up inside the project.
Two separate routes, and both need a little more than "clicking on the folder":
The agent hook fires when you start Claude Code in that directory. No npm install, no build: the startup command itself is the trigger. On the very first start in a new directory, though, the trust prompt gets in the way, and that one is a real barrier. In a project you have already confirmed once, claude is enough after that, and the hook runs along with it.
The editor task hangs off runOn: "folderOpen" in .vscode/tasks.json. It only runs when two things come together: the folder counts as trusted (Workspace Trust) and automatic tasks are allowed there. Those are two separate switches, one on its own isn't enough.
The practical conclusion stays the same, it's just more precise now: anyone who thinks a stranger's project is harmless as long as they only look at it and don't build it is wrong. In a project you have already approved, your own tool starting up is enough. With the editor it takes two approvals, and trusting the folder only clears the first one: automatic tasks have to be allowed on top of that. That is the difference between "it can happen" and "it happens to me".
Then there's the spread: with a stolen access token the worm distributes the same hooks across up to 50 branches per reachable repository. A single compromised machine can seed a whole organisation with prepared configuration files.
3. What it's after
The collector in the malicious code checks over 300 patterns for credentials. And it targets Anthropic, Claude, Codex, Cursor and Gemini access by name.
That is the real news in this incident. AI credentials are no longer a side catch that a collector stumbles across, they are an explicit target. If you have an API key sitting in a configuration file, treat it from now on the way you treat your banking password.
4. The immediate check, four passes
Go into every project you've opened in recent weeks and look for configuration files you didn't put there yourself.
# Is there a project config you don't recognise?
ls -la .claude/ .vscode/ 2>/dev/null
# What's in it? Look for hooks and tasks that fire on startup.
# Don't forget settings.local.json: it is project-local, usually sits in the
# .gitignore and can carry hooks just the same.
cat .claude/settings.json .claude/settings.local.json 2>/dev/null
cat .vscode/tasks.json 2>/dev/null
# Who touched these files when, across ALL branches, not just the current one?
# -p shows the CONTENT of the change as well. Without it you only see THAT
# somebody touched something, and a plausibly named commit slips right past you.
git log --all -p --format='%h %an %ad %s' --date=short -- .claude/ .vscode/
# And is anything sitting there uncommitted?
# --ignored matters here: .claude/ is in many projects' .gitignore, and a plain
# status call then cheerfully reports "nothing to see".
git status --short --untracked-files=all --ignored=matching -- .claude/ .vscode/
What should make you pause: an entry that downloads something on startup and runs it straight away. A long string that looks like encoded text. A command reaching for an address on the internet that has nothing to do with the project.
The provenance question works out differently depending on the file. With the shared files (settings.json, tasks.json) the project history is the yardstick: anything sitting there that you can't trace back to a deliberate change needs checking. With settings.local.json that doesn't work, because it is local on purpose and normally is precisely NOT in Git. There the yardstick is your own memory: did you create that hook yourself? If not, somebody else wrote it in there.
The last two commands are the important ones, and --all is no small detail there. Without that flag you only see the history that is reachable from where you're currently standing. That is exactly what would miss the spread across up to 50 branches from step 2: the branch carrying the hook would be invisible, and the check would report all clear. %an also shows you the author, because a commit under a name you don't recognise is a signal in itself.
A configuration file appearing in a commit whose message describes something entirely different is a warning sign regardless of what the file contains.
What these commands don't cover: branches that only exist at your code host and that you never fetched, plus forks and pull requests. You go through those in the browser, and from a device you trust.
5. The disagreement you need to know about
Now the part where you'll find two recommendations that contradict each other. Both come from serious analysts, and both have a case.
One side (JFrog, Microsoft, Unit 42) says: rotate credentials immediately. Standard practice for any data leak, every minute counts, a stolen key gets used.
The other side (SANS Internet Storm Center) describes a dead man's switch. The malicious code installs a small service that checks the stolen access token every minute. When the token becomes invalid, which is exactly the moment you revoke it, it executes a remotely controlled response. On that reading, revoking is the trigger for the next stage. SANS therefore recommends: disconnect first, clean up, then rotate.
The contradiction is real, and I'm not going to dissolve it here with a compromise. When the field disagrees, you don't get a clean recipe, you get a decision.
What the argument covers, and what it doesn't. Both sides are talking about the compromised machine. On one thing they agree, and it's the more important one: your credentials may already be gone before you noticed anything at all. An attacker who has them uses them from his own machine. It doesn't bother him in the slightest whether your computer is on the network.
That is why "disconnect first, then decide at your leisure" is wrong, and I had it written that way here at first. Isolation only protects against NEW data leaving and against the malicious code receiving NEW commands. It does not protect what is already gone. Anyone who postpones the revocation until the cleanup is finished hands the attacker exactly that window for your repositories, cloud accounts and pipelines.
The route that brings both together:
- Take the affected machine off the network. That part is uncontested and takes seconds.
- Then revoke immediately, but from a different device that is demonstrably clean. Your phone, a second computer, a colleague's machine. The compromised one stays offline while you do it.
The attacker therefore loses the access the moment you notice, instead of hours later.
What that does to the dead man's switch, honestly: it isn't defused, only postponed. A service with no network usually cannot tell that the token was revoked at all. The trigger stays armed and waits for the next connection. That is exactly why the last step in this situation isn't "back online", it's rebuilding the machine.
What you must not do here: put the compromised machine back on the network "just for a minute" to rotate the keys. Then the new ones are gone just as fast as the old ones.
6. The order, if you found something
- Disconnect the network. Wi-Fi off, cable out. Before every other step, and the machine stays offline until step 6.
- Revoke everything, from a clean device. Phone or second computer. AI keys, code hosting tokens, cloud access, active sessions. Everything that was reachable from the compromised machine, not just the obvious things. You generate the new keys there as well, and they only go onto the compromised machine once it counts as clean again.
- Preserve evidence. Copy the suspicious files somewhere safe before you delete them. Without them you can't say afterwards what happened. Treat that copy as contaminated: it is evidence, not a backup to restore from.
- Look for what nested itself in. Deleting a startup hook isn't enough if a service sits next to it that puts the hook back. Check your system's user services and your autostart entries.
- Remove the configuration files and revert the change in the project, on all branches, not just the one you happen to be standing on. The branches you don't have locally you check at your code host in the browser, from the clean device.
- Rebuild the machine before it goes back on the network.
Why step 6 is that harsh. Deleting configuration files and removing visible autostarts is containment, not proof. With a worm that propagates itself, deliberately harvests credentials and carries a dead man's switch, one more thing applies: the trigger never fired while offline, it is waiting. Reconnecting after a hand cleanup hands it exactly that moment.
That is why the reliable route is rebuilding from a trusted image. It sounds harsh for a work machine, but it is the only state where you know afterwards where you stand. Anyone who can't or won't do that carries on with a known residual risk and should at least do so knowingly, rather than treating it as settled. Either way, the new keys only go onto that machine once it counts as clean.
If step 4 leaves you unsure, that's the point where professional help is cheaper than guessing. A half-cleaned system is worse than a known-infected one, because you stop looking.
7. The npm question, also unresolved
A second point where the sources differ. JFrog writes that under newer npm versions install scripts no longer run by default, so the malicious code doesn't fire during installation there. Microsoft describes execution as the normal case.
Both can be right, because it depends on your environment. But the version number alone does not answer the question. What counts is the setting that is actually in effect, and that can come from the global configuration, from an .npmrc in the project or from an environment variable. So ask the setting itself, not the version:
npm config get ignore-scripts
If that says false, install scripts run on your machine, no matter which npm version you have. And even if it says true: don't rely on it, spell it out on the actual run.
npm ci --ignore-scripts
Above all though: this only protects one side. Against the startup hook from step 2 the setting does nothing at all, because that route has nothing to do with npm. Two different routes, and only one of them is touched here.
8. Permanently: treat hooks like code
The real consequence isn't the one worm, it's a habit.
A configuration file that runs commands on startup is executable code. It just doesn't look like it, because it lives in a folder with a dot in front of its name and nobody reads it during review. That's exactly why the attack works.
Three habits that fix this:
First, .claude/settings.json, .claude/settings.local.json and .vscode/tasks.json deserve the same attention as a file full of program code. When you work in somebody else's project, look inside all three once before you start the tool.
Second, keep the three scopes cleanly apart. There is personal and global (applies everywhere, belongs in your user configuration), project-wide and shared (.claude/settings.json, versioned along with the team) and project-local and personal (.claude/settings.local.json, stays with you). A hook that only you need, and only in this project, belongs in the third drawer, not in the global one. Push it to global and it runs in every repository from that moment on, growing your attack surface instead of shrinking it.
Third, for unfamiliar projects off the internet the same caution applies as for unfamiliar MCP servers. How to vet those is in Vetting third-party MCP servers, and the procedure carries over almost one to one.
9. If you work in a team
In a shared repository the project configuration is a shared attack surface. Whoever can change it changes the startup conditions for everyone on the team.
So treat changes to these files as changes that require review. No waving them through. Anyone adding a startup hook writes down what it does and why, the same as for any other change to code that runs in production.
If your pipelines run automatically, a second layer comes on top: that's where external content often gets processed unfiltered. That's a topic of its own, Running Claude Code headless covers the mechanics.
10. The lesson that sticks
A startup hook is useful precisely because it runs without asking. Which is exactly why it's a target. That isn't a weakness of the tools, it's the flip side of convenience, and it applies to every automation you build for yourself.
The practical conclusion is unspectacular: look once before you open somebody else's project. Keep your credentials out of files that travel with the project. And if something starts automatically somewhere, know what it does.
What comes next
If you're going through your hooks anyway now, Debugging hooks when nothing fires is the natural neighbour: same files, opposite direction. For the credentials side, Securing MCP over stdio is the entry point.
A word on backups, because the order is different here for once: Memory backup before an update covers backups for the normal case, and there "secure first, then touch" is right. Not when you suspect a compromise. There it goes: isolate first, revoke from the clean device second, and only then preserve evidence, before you change anything locally. A copy created after a possible infection is evidence, not a restore point. Only a state from before the incident is worth restoring. Confuse the two and you pull the malicious code back out of your own backup.
Source
As of 9 August 2026. The incident is five days old and still under investigation, details may change. The sources on the points in steps 5 and 7 remain contradictory; this playbook lays the contradiction open and draws a cautious course of action from it, rather than pretending there is one unanimous recipe.
- Elastic Security Labs on the worm analysis: elastic.co/security-labs
- JFrog on the spread through npm: research.jfrog.com
- Microsoft on the anatomy of the self-propagating worm: microsoft.com/security/blog
- Unit 42 with a further independent analysis: unit42.paloaltonetworks.com
- SANS Internet Storm Center on the dead man's switch and the differing order: isc.sans.edu/diary/33218