The npm Worm and Your Website: A Front-End Build Is Now a Security Boundary
On 4 August 2026 a self-replicating worm reached the keyv caching library and its relatives, and within hours had spread to hundreds of npm packages representing billions of installs a month. It was the third and largest wave of the campaign known as Shai-Hulud, and it landed a week before this was written.
Most business owners will assume this is a developer problem. It is not, for one specific reason: if your website is built with a modern front-end toolchain, then the machine that assembles your site downloads and executes code from hundreds of authors every time someone runs a build. That build step is a security boundary, and until recently almost nobody treated it as one.
What actually happened
The pattern has been consistent across waves and is worth understanding once, because the details explain why ordinary precautions did not help.
- It runs before you use the package. The malicious code executes during installation, in a lifecycle hook, not when your application calls the library. Installing is enough.
- It harvests credentials. Environment variables, cloud keys, tokens on the machine — the things a build server has by definition.
- It republishes itself. With a stolen publishing token it pushes malicious versions of other packages the victim maintains. That is what makes it a worm rather than an incident, and why hundreds of packages fall in hours.
- It exfiltrates in the open. Earlier waves pushed stolen data into thousands of newly created public repositories, which is noisy, effective and hard to clean up.
The November 2025 wave compromised several hundred packages and created tens of thousands of malicious repositories; published analyses document the mechanism in detail. The August 2026 wave was larger again, reaching packages that almost every JavaScript project pulls in indirectly.
The uncomfortable arithmetic: nobody chose to install keyv. It arrives as a dependency of a dependency of a tool you did choose. A typical front-end project resolves to several hundred packages, of which a developer has deliberately selected perhaps fifteen. Every one of the rest can execute code on the build machine, and the number of maintainers who could be compromised to reach you is the same several hundred.
Whether this affects your site
Three cases, three different risk levels. Find yours before deciding anything.
| How your site is built | Exposure | What matters |
|---|---|---|
| Classic WordPress theme, no build step | Low from this specific attack | Your risk is plugins, not npm |
| WordPress with a compiled theme or block plugins | Real, but confined to build time | Who builds, on what machine, with which credentials |
| Headless, SPA or a JavaScript framework | High | Every deploy is a fresh install of hundreds of packages |
| Site builder or hosted platform | Inherited | The platform’s supply chain is now yours |
Note the second row. A great many WordPress sites do have a build step — a theme compiled from source, custom blocks, a bundled admin script — and the person running it is often a contractor on a laptop that also holds client credentials. That laptop is the target.
What actually reduces the risk
Five measures, ordered by effect per unit of effort. None requires a security team.
- Build somewhere disposable. A container or CI runner that is created for the build and destroyed after it. A stolen credential is only useful if it exists on the machine; a clean runner with narrowly scoped secrets limits the blast radius of any compromised package to that one job.
- Disable install scripts where you can. Configuring the package manager to skip lifecycle scripts blocks the exact mechanism these waves use. Some packages genuinely need them, so this requires a short allowlist rather than a global switch — but it converts a silent execution into a deliberate decision.
- Pin and commit the lockfile, then delay upgrades. Most of these campaigns are caught within hours to days. A policy of not installing a version published in the last seventy-two hours, outside of security fixes, removes most of the exposure at almost no cost.
- Stop using long-lived publishing tokens. npm revoked classic tokens in December 2025 and capped write-capable granular tokens at ninety days, with session-based authentication replacing them. If you publish packages, move to trusted publishing via your CI provider; the OpenJS guidance is the practical reference.
- Separate credentials by purpose. The token that deploys should not be the token that reads your database, and neither should live in a developer’s shell profile. This is the same principle as the access register described in our note on what a client’s security clauses actually ask for.
The questions to ask your contractor
If someone else builds and deploys your site, four questions establish your actual exposure. They are reasonable questions and a competent supplier will have answers ready.
“Where does the build run?” A named CI service is a good answer. “On my machine” is honest and means the risk is one laptop’s hygiene.
“What credentials exist on the build machine?” You are looking for a short list scoped to deployment, not a laptop holding production database access and the keys to five other clients.
“Do you commit a lockfile, and do you review dependency updates?” A lockfile that is committed and rarely changed without review is the difference between deterministic builds and a lottery.
“What happens if a dependency is found to be malicious tomorrow?” The answer should include rotating credentials, rebuilding from a known-good state and checking what the build machine could reach. If there is no answer, that is the gap.
If you think you were exposed
Speed matters more than certainty. Assume compromise and work in this order.
Rotate every credential that existed on the affected machine or in the affected pipeline: npm tokens, cloud keys, deployment keys, database passwords, API keys for third-party services. Rotation is cheap and reversible; investigation before rotation is neither.
Then check what those credentials could reach. A deployment key that only writes to a staging bucket is a different incident from an admin key on a production database.
Rebuild from a clean checkout on a clean machine rather than “removing the bad package”. The malicious code has already run; uninstalling it removes the package, not the consequences.
Finally, look for what it left behind: new repositories under your organisation, new deploy keys, new collaborators, unexpected workflow files. These campaigns create artefacts, and the artefacts are how you confirm scope. This is also the moment when a restore-tested backup stops being paperwork, for the reasons in our note on what to test before you need it.
The same pattern outside npm
npm is the loudest example because it is the largest registry and the one that executes third-party code at install time by default. But the shape of the attack — compromise one maintainer, inherit everyone who depends on them — is not specific to JavaScript. Three neighbouring cases matter more for an ordinary business site than a worm in a build pipeline, because they reach production directly rather than through a developer’s laptop.
WordPress plugins. A plugin with a hundred thousand installs gets sold, inherited or quietly handed over, and the update channel goes with it. Code arriving in an automatic update has exactly the privileges of the code you reviewed on the day you installed it: full access to the site and its database. So the question “who publishes this plugin now, and are they the people who wrote it?” belongs beside the version number in any review. Abandoned plugins are the slower version of the same problem — nobody has to be malicious for an unmaintained plugin to become the way in. The control that works is a short inventory that somebody actually maintains, rather than a long list nobody has read since launch; that inventory is one of the things a real support arrangement is for.
Browser extensions. The same acquisition pattern hits extensions, and here the blast radius includes every admin session open in that browser. A developer’s browser is frequently the least controlled machine with access to your production site, and it is the one place where a compromise needs no build step at all.
Anything fetched at build or page load that is not a package. Fonts, icon sets, analytics snippets and CDN scripts loaded from a third-party URL are dependencies without a lockfile: the file behind that URL can change without any version number changing, and nothing in your process would notice. Self-hosting them settles the tampering question and the privacy question in one move.
The common thread is trust granted once and never revisited. Every one of these channels was approved at some point by someone who is no longer looking at it, which is a maintenance problem wearing a security costume.
What this changes about how sites are built
Two shifts are already visible and both affect procurement rather than code.
Dependency count is now a cost, not a neutral fact. A framework that pulls three hundred packages to render a brochure site is carrying risk that a simpler stack does not. That argument used to be about page weight and is now about exposure — a consideration that belongs beside the ones in our note on what breaks in hastily generated sites, where dependencies accumulate faster than anyone reviews them.
And the build environment is now part of the deliverable. A contract that specifies the website but says nothing about where it is compiled, with which credentials and by whom, has left out the part that this year’s attacks target. Adding those three lines costs nothing at signature and is close to impossible to retrofit during an incident.
Key takeaways
- The August 2026 wave was the largest so far, reaching
keyvand hundreds of related packages within hours through self-propagation. - The malicious code runs at install time, not at runtime, so simply installing a dependency is enough.
- If your site has a build step, you are in scope — including many WordPress sites with compiled themes or custom blocks.
- The highest-value measures: disposable build environments, disabled install scripts with a short allowlist, committed lockfiles, delayed upgrades, and no long-lived publishing tokens.
- npm revoked classic tokens in December 2025 and capped write-capable granular tokens at ninety days; trusted publishing via CI is the intended replacement.
- If exposed, rotate first and investigate second, then rebuild clean and look for repositories, keys and collaborators you did not create.








