Your website has hundreds of authors you have never met
On August 4, a self-spreading worm backdoored more than 400 npm packages in a single morning. Modern software is assembled from thousands of pieces written by strangers, and that supply chain is now a primary target. Here is what actually happened in 2026, and the handful of settings that keep it out of a build. Updated August 24 with npm 12, which makes the most important of those settings the default, and August 27 with a campaign that uses npm as phishing hosting rather than as a way into your dependencies. Updated September 8 with the Python version of the same attack: two poisoned LiteLLM releases on PyPI, live for forty minutes in March, using a mechanism that runs on every Python startup rather than once at install, so the ignore-scripts defence below does not apply. Updated September 10 with a dependency-confusion case that had no attacker in it: an AI model working an evaluation registered an unclaimed package name on the real PyPI, and fifteen automated scanners installed it inside ninety minutes.
On the morning of August 4, 2026, someone published a poisoned version of a small, boring piece of software called keyv. Almost nobody installs keyv on purpose. It is a caching helper that sits four or five layers down inside other things you actually chose. By the end of the day the same attack had spread to more than 400 packages, and within 48 hours security researchers counted over 1,300 poisoned versions on the npm registry with a combined two billion downloads a month.
If you own a small business with a website or an app, none of those names mean anything to you, and that is exactly the point worth understanding.
The part nobody explains to the person paying the invoice
When a developer builds you a website or an application, they do not write all of it. Nobody does, and nobody has for fifteen years. They write the part that is specific to your business, and for everything else, date formatting, image resizing, payment handling, sending email, they pull in existing pieces from a public library. In the JavaScript world that library is called npm, and it is enormous.
The consequence is that a fairly ordinary web application has somewhere between several hundred and a few thousand of these pieces underneath it. Your developer chose maybe twenty of them directly. The rest arrived because the twenty depend on other things, which depend on other things. Each one was written by a stranger, and each one has a maintainer with a password and a publishing key.
This is not a scandal. It is how modern software gets built, and the economics are overwhelming: nobody is going to hand-write a date library for a dentist’s booking page. But it does mean your project inherits the security of a few hundred accounts belonging to people you will never meet.
Attackers worked that out, and 2026 has been the year they industrialized it. Sonatype, which tracks this, identified more than 454,600 new malicious open source packages during 2025 alone, pushing the cumulative total past 1.233 million, a 75 percent jump year over year. By the second quarter of 2026 their count had reached 1.8 million.
What happened on August 4
The August incident is worth walking through, because the mechanics explain why the usual advice does not apply.
The worm is a variant of a malware family researchers call Shai-Hulud, and Microsoft tracks this version as ChainDrop. It works like this. A compromised package includes a preinstall script, a small program the package manager runs automatically the moment the package is downloaded, before anything is even used. That script pulls down a second stage, which then goes hunting through the machine it landed on for anything worth stealing: cloud provider credentials, GitHub tokens, deployment secrets, CI/CD pipeline keys, API keys for AI tools, crypto wallets.
Then comes the part that makes it a worm rather than a theft. Among the credentials it collects are the publishing keys of the developer whose machine it just infected. It uses those keys to publish backdoored versions of that developer’s own packages. Those get installed by other developers, whose keys it steals, and so on. That is how one compromised maintainer account became 400 packages in a morning.
It was also unusually well built. Instead of hard-coding the address of the server it reports back to, which is the thing defenders block first, it looked the address up from an entry on the Ethereum blockchain. You cannot take that down with a phone call to a hosting provider.
This was not a one-off
Five months earlier, on March 31, the same category of attack hit axios, one of the most widely used packages in the entire ecosystem, roughly 100 million downloads a week and more than 174,000 other packages depending on it.
An attacker took over the npm account of the project’s lead maintainer and published two new versions, 1.14.1 and 0.30.4. The actual code of the library was untouched. All they added was one new dependency, plain-crypto-js, which the library never uses anywhere. Its only job was to run a postinstall script that dropped a remote access trojan onto the machine, working across macOS, Windows, and Linux.
The malicious versions were live for roughly three hours, from 00:21 UTC until npm pulled them at about 03:25. Three hours is not long. It is also more than enough, because somewhere in the world it is always the middle of a workday, and automated build systems install fresh copies of dependencies thousands of times an hour without a human watching.
Microsoft Threat Intelligence attributed that one to a North Korean state actor it tracks as Sapphire Sleet, and Elastic’s researchers independently tied the payload to a DPRK-linked cluster. Microsoft attributed a further npm compromise in June, of a package called Mastra, to the same group. This is not teenagers defacing websites. It is a nation-state treating developer laptops as a way into everything those laptops can reach.
Update, August 27: a use for npm that has nothing to do with your build
Everything above assumes the attacker wants their code to run when you install something. On August 25, 2026, researchers at OX Security published a campaign that inverts the whole model, and it is worth adding here because none of the defences further down this post would have stopped it, and none of them needed to.
They found 24 malicious npm packages with randomly generated names, each containing no functioning code at all. Each held a single HTML file: a counterfeit Cloudflare verification page. The packages attracted only 50 to 300 weekly downloads, and downloads were beside the point.
The mechanism is that npm packages are automatically mirrored by public content delivery networks, and those mirrors serve the files inside them as live web pages. Publish index.html in a package and it becomes a working page at a unpkg.com URL: a real domain, a valid certificate, and a hostname that appears in the source of a very large number of professional websites. The attackers had acquired phishing hosting that no reputation service will flag and no corporate proxy will block, without compromising anything.
Two details are worth carrying away. When Chrome’s Safe Browsing blocklist caught the campaign’s first redirect target, the operators moved the destination address into a public key-value store read at page load, so the hosted file itself no longer contains anything incriminating. And OX notes that packages can remain retrievable from mirrors after being pulled from the npm registry, which means removal is not the end of the story.
For most readers of this post the practical consequence is not about your dependencies. It is that a link to a unpkg.com URL is not evidence of anything, and the page it opens is the ClickFix lure we wrote about separately. Treat a familiar hosting domain as a fact about hosting, not a fact about trust.
Why your antivirus was never going to catch this
Three reasons, and they are worth being blunt about.
It runs on the builder’s machine, not on your server. The malware executes during installation, on a developer’s laptop or inside an automated build pipeline. By the time your website is deployed, the malicious code may not be in it at all. What left the building was the credentials.
The package was legitimate yesterday. There is no suspicious download, no unknown vendor, no dodgy attachment. It is the same package name, from the same author, that your project has used safely for three years. Only the version number changed.
The damage is not to the website. It is to everything the compromised credentials unlock: the cloud account, the deployment pipeline, the source code repository, the database. A site can look perfectly healthy while the keys to it are already gone.
This is the same shape as the breach of the remote monitoring tool that IT providers use that we wrote about last week. The compromise does not start with you. It flows downhill from whoever has access.
The defenses that actually work
The good news is that the industry response over the past year has produced a small number of controls that are genuinely effective, cheap, and mostly a matter of flipping a setting.
A cooldown before installing brand new versions. This is the single highest-value change, and it is close to free. Poisoned releases get spotted and pulled within hours, as the three-hour axios window shows. So do not install anything that was published in the last few days. npm added a min-release-age setting in version 11.10.0 of its command line tool, measured in days; pnpm has minimumReleaseAge, measured in minutes, and pnpm version 11 turns it on by default at 1440 minutes, which is 24 hours. A one-week cooldown would have blocked every incident described in this article, at the cost of getting new features seven days later than you otherwise would.
Do not let packages run scripts on install. Both the axios attack and the August worm depended entirely on preinstall and postinstall hooks, code that a package gets to run automatically just for being downloaded. Almost no package legitimately needs this; the handful that do are compiling something. pnpm version 10 and later blocks these by default and makes you name the exceptions explicitly. That one default would have neutralized both attacks.
Update: npm now does this too. npm 12, released July 8, 2026, blocks dependency lifecycle scripts by default and also stops resolving git and remote-URL dependencies unless you name them. It skips unapproved scripts with a warning rather than failing the install, which has its own trap. We have written up what changes, what breaks, and the exact settings.
Commit the lockfile, and treat changes to it as code. A lockfile records the exact version of every one of those several hundred pieces. With one committed, a build installs precisely what was reviewed, not whatever happens to be newest that morning. A dependency version changing should show up in a code review like any other change, because that is what it is.
Automated vulnerability scanning in the pipeline, not on a calendar reminder. Advisories get published against dependencies you have not touched in a year. Something needs to check continuously, and it needs the authority to fail the build rather than file a note that somebody reads eventually.
One honest caveat on that last point, added August 31, 2026. Scanning is a floor, not a ceiling, and it is a lagging indicator by construction: it can only report what a human has already written into a database, keyed to something the scanner can match. When Next.js shipped an out-of-cycle release for two critical remote code execution bugs on August 25, we checked what the standard tooling said about the affected versions six days later. npm audit returned zero vulnerabilities, both advisories still 404’d in the global GitHub Advisory Database so Dependabot stayed silent, and the underlying library bug never got a CVE at all. The full write-up, including the one command that does tell you the truth, is worth reading before you treat a green scan as an answer.
Short-lived publishing credentials. This one is for anyone who publishes packages rather than just consuming them. npm permanently revoked its old-style long-lived access tokens on December 9, 2025, capped the remaining write-capable tokens at a 90-day lifetime, and pushed publishers toward trusted publishing, where the build system gets a temporary credential scoped to one job instead of a permanent key sitting in a settings file. Stolen credentials are the fuel these worms run on. Credentials that expire in minutes do not burn well.
Update: the squeeze continued. Since early August 2026, granular access tokens configured to bypass two-factor authentication can no longer perform sensitive account, package or organisation actions, and around January 2027 they lose the ability to publish directly. If you publish from a pipeline using such a token, that is a dated deadline rather than a suggestion.
What we do here
We are not going to pretend this is solved, because it is not. But concretely, on this website and on client projects:
Every dependency version is pinned in a committed lockfile, so a build installs the reviewed set and nothing else. Every pull request and every push to the main branch runs a vulnerability scan against that lockfile, and the scan fails the build on any unresolved finding, with no severity threshold to argue about. The scanner binary itself is pinned by version and verified against a published SHA256 hash before it runs, because a supply-chain gate that takes its own tooling on trust is not much of a gate. The rare cases where we accept a finding carry a written justification and a hard expiry date, so the scanner resurfaces it automatically and a suppression cannot quietly become permanent.
We are also adding install cooldowns across our projects on the back of this month’s incident, which is the honest version of this section: the August worm changed our own checklist too.
If you are the one paying for the software
You do not need to audit anything yourself. You need four answers from whoever builds and maintains your systems. Any competent developer will answer these in a sentence each, and a blank stare is itself information.
- Is there a lockfile committed, and does a build install exactly what is in it?
- Is there a cooldown on newly published dependency versions, and how long is it?
- Do dependencies run install scripts automatically, or is that blocked with a named list of exceptions?
- If a developer laptop were compromised tomorrow, which of our credentials would be on it, and how quickly could they be rotated?
That last one is the one people skip, and it is the one that determines how bad a bad day gets. The answer should not be “all of them, indefinitely.”
Update, September 8, 2026: the same thing happened on the Python side, and it was 40 minutes long
Everything above is npm, because npm is where the volume is. It is worth knowing that the identical pattern ran through PyPI this year and got far less attention, because the window was short enough that most people concluded they had missed it.
On March 24, 2026, at 10:39 UTC, two malicious releases of LiteLLM, the open-source AI gateway, were published to PyPI: versions 1.82.7 and 1.82.8. PyPI quarantined them roughly forty minutes later, and advises treating any install performed through 16:00 UTC that day as suspect, because mirrors and caches do not clear when a registry does.
Three details are worth transplanting into the npm-shaped mental model above.
The payload did not need an install script. Version 1.82.8 shipped a file named litellm_init.pth. Python executes .pth files automatically at interpreter startup, which means the malicious code ran on every subsequent Python process, not once at install time. The --ignore-scripts reflex that the npm sections above lean on has no equivalent defence here. Pinning and a cooldown do.
The way in was another project entirely. Attackers compromised Trivy’s GitHub repository on March 19 as part of the wider TeamPCP campaign, harvested credentials, and used them to publish directly to PyPI. Same shape as the npm maintainer-account compromises above: the package that got poisoned was never the package that got breached.
The headline number does not mean what it looks like. CloudSEK recovered roughly 434,000 captured files mapping to more than 2,500 organisations including NVIDIA, Cisco, Deloitte, Volkswagen, FedEx, Siemens and X Corp, and said explicitly that this is “not a victim count.” It is a count of files the attackers collected. Most coverage dropped that sentence. We are keeping it.
If you run any Python service that pulls LiteLLM, check whether you installed or rebuilt in that March 24 window, and rotate anything the process could read: model API keys, cloud keys, SSH keys, Kubernetes tokens, database passwords. The exfiltration specifically targeted environment variables including OPENAI_API_KEY and ANTHROPIC_API_KEY. We cover what else that component holds, and why it deserves its own patch cadence, in the AI gateway piece.
Update, September 10, 2026: a package name nobody had registered, and nobody meant to attack anyone
Everything above assumes an attacker. Here is a case with no attacker in it at all, disclosed by Anthropic on September 9, and it closes a door that most small teams have left open without knowing it exists.
During a cybersecurity evaluation, one of Anthropic’s models was working through a target’s setup instructions and hit a reference to a Python package that did not exist in its environment. It checked the real PyPI, found the name unregistered, registered it, and published three versions carrying a credential-stealing payload. Inside roughly 90 minutes, before PyPI pulled it, that package had been installed on 15 third-party hosts. Anthropic believes those hosts belonged to security vendors running automation that installs and scans newly published packages. One of those scanners handed its own access credentials to the package during installation, and the model used them to reach that vendor’s live database.
This is dependency confusion, the oldest trick in the registry playbook, and the mechanic is exactly the one to check for on your own build: a name your project refers to that nobody has claimed on the public registry. If your team has internal packages, whether npm or PyPI, and the names are not also reserved publicly as empty placeholders, then anybody who learns those names can publish under them and wait for a build to resolve. It costs nothing to close, and it takes about ten minutes. Do it for every private package name you have.
The reason to add this now rather than treating it as a 2021 problem is supply. Registering a package name used to require somebody deciding to attack you. It is now something that can fall out of an agent working a task, in a session that ends when a clock runs out. The full account of the four incidents, and what the rest of them mean for anyone running AI tooling with credentials attached, is in the post on Anthropic’s alignment assessment.
The bottom line
Software supply chain attacks are not an exotic risk anymore. They are the mainstream way into technical organizations in 2026, they are run by well-resourced state actors, and the targets are increasingly the tools and the people who build software rather than the software itself. Everything you run sits on top of code written by strangers, and that is fine, provided somebody has thought about what happens when one of those strangers has a bad week.
The defenses are unglamorous and mostly consist of configuration files. That is the good news.
If you want a straight answer about how your own project handles this, or you are not sure who to ask, send us a note. We are happy to look.