← CYBERNETICSINTERN.COMCYBERNETIC INTERN
Supply chainIncident breakdown

A worm got into npm through one maintainer's account

On August 4, 2026 a self-replicating worm hijacked the keyv npm package family, stealing developer credentials and using them to infect hundreds more packages.

Researched and drafted with AI assistance, then reviewed and edited by Shreyas Lipare before publication. Every source below was checked against the original.

Most supply chain attacks ask you to make a mistake. This one did not.

On August 4, 2026, someone took control of the GitHub account belonging to the maintainer of keyv (a small, boring, extremely popular caching library) and published poisoned versions of it and its sibling packages [2][3]. Within hours the malicious code had used credentials it stole from the machines that installed it to publish itself into hundreds of further packages [1].

You did not have to typo a package name. You did not have to install something obscure. You had to run npm install on a Tuesday.

What happened

The compromised account belonged to the maintainer of a family of caching utilities that sit deep in the JavaScript dependency graph: keyv, cacheable, flat-cache, file-entry-cache, cacheable-request, cache-manager and several @cacheable scoped packages [1]. These are not packages most teams choose. They are packages that arrive underneath something else: a build tool, a linter, a test runner.

The attacker pushed malicious files into the repositories and cut releases immediately, so the poisoned versions went out through the projects’ own publishing pipelines [3].

How big it got depends on when you looked, and this is worth stating plainly because the numbers in circulation do not agree. JFrog counted more than 400 packages across more than 1,700 versions on August 4, 2026 [2]. Aikido reported at least 444 packages across 1,381 versions in an update the same day [3]. By August 6, 2026, Singapore’s Cyber Security Agency described over 1,300 package versions representing roughly two billion monthly downloads [1]. Those are not contradictions so much as timestamps, the worm was still spreading while people were counting.

The campaign is a return of the Shai-Hulud malware family, and it leaves a calling card: CSA advises organisations to watch for GitHub repositories whose description contains the phrase “Shai-Hulud: Here We Go Again” [1].

Why this matters

Two things about this incident should change how you think, not just what you patch.

The first is that provenance held, and it did not help. npm provenance exists to prove a package was built and published by the workflow it claims to come from. Here, the malicious releases carried valid provenance signed through GitHub Actions, because the attacker put bad code into the repository and let the real, trusted pipeline publish it [3]. JFrog documents a further step against certain repositories, where the malware requested a GitHub Actions OIDC token, exchanged it for npm publishing rights, and obtained a valid signature through the normal certificate transparency machinery [2].

The signature was honest. It attested to the build, not to the intent. If your supply chain policy is “require provenance,” you have a control that would have waved this through.

The second is that the blast radius is your secrets rather than your software. The payload’s purpose is credential theft. JFrog’s analysis lists npm and PyPI tokens, AWS, Azure and GCP configuration, Kubernetes secrets, SSH keys, browser credential stores, credentials for AI services including OpenAI and Anthropic, and on Linux hosts, the shadow password file. On GitHub Actions runners it goes after the runner’s own process memory to reach secrets held there [2].

For most organisations, the developer laptop and the CI runner are the two most over-privileged machines in the estate. This malware was written by someone who understands that.

Technical breakdown

The route from one hijacked account to hundreds of poisoned packages runs like this. It is described here to help you find and stop it; the specifics of how the payload is constructed are deliberately left out.

ATTACK CHAIN
  1. Initial accessTakes over the GitHub account of a maintainer who owns a family of widely depended-on caching packages
    BREAK THE CHAIN HERE
    Hardware-backed, phishing-resistant MFA on every account that can publish, plus branch protection so a single account cannot push straight to main and cut a release. You cannot enforce this on other people's maintainers, which is the argument for pinning dependencies and reviewing what your lockfile actually resolves to.
  2. ExecutionPushes malicious files to the main branch and cuts a release, so the project's own trusted pipeline publishes and signs the result
  3. DeliveryThe poisoned version reaches developer machines and CI runners as a transitive dependency, running code at install time
    BREAK THE CHAIN HERE
    Move to npm 12 or newer, where preinstall lifecycle hooks do not run by default, or install with --ignore-scripts and allowlist the few packages that genuinely need a build step. Partial cover only: this malware family has also used import-time execution and Python startup files to run without an install hook at all.
  4. Defence evasionFetches a separate JavaScript runtime so the payload does not depend on how the local environment is configured
  5. Credential accessSweeps the filesystem, and on build runners the runner process itself, for registry tokens, cloud keys, SSH keys and AI service credentials
  6. PersistenceIdentifies stolen publishing tokens that are permitted to publish without a second factor
    BREAK THE CHAIN HERE
    Revoke every registry token that can publish unattended and move to trusted publishing with short-lived, workflow-scoped credentials. This is the step that decides whether a compromise stays a credential leak or becomes a worm. Detection: alert on package publishes that did not originate from your release workflow, and on patch versions you did not tag.
  7. PropagationRepublishes other packages those tokens can reach, incrementing the patch version so the release looks routine
  8. ExfiltrationPushes the harvested credentials out through newly created public repositories and commit messages

Three steps carry a break, where a named control severs the chain.

Phishing-resistant MFA on maintainer accounts addresses the first. Every downstream event here follows from one account takeover. No source I could open states how that account was taken, so I will not speculate about the mechanism but the class of control that protects a maintainer account is well understood and it is hardware-backed authentication.

Not executing install-time code addresses the third. JFrog notes that on npm 12 and newer, preinstall lifecycle hooks do not run by default [2]. That is a meaningful default change. It is not a complete answer, because this malware family has previously used import-time execution and Python startup files to run without an install hook at all [2], but it removes the path used here.

Eliminating publishing tokens that bypass two-factor authentication addresses the sixth, and it is the one that stops a compromise from becoming a worm. The malware specifically looks for tokens permitted to publish without a second factor [2]. A stolen token that cannot publish unattended is a credential leak. A stolen token that can publish unattended is a propagation engine.

The exfiltration path is unusual enough to be worth knowing for detection. JFrog describes stolen material being pushed to newly created public repositories named after Dune terminology, with credentials encoded into commit messages, and endpoint addresses resolved through an Ethereum smart contract so that takedowns of a single server do not break the channel [2]. If your organisation suddenly has new public repositories nobody remembers creating, that is not a curiosity.

What defenders should do Monday morning

Immediate: today.

Search lockfiles, build logs and CI caches for the affected package versions around August 4, 2026, not just your current dependency tree, because a bad version may have been installed and since replaced [1]. If any affected version ran anywhere, treat that host or runner as compromised; JFrog is explicit that removing the package afterwards does not undo what already executed [2]. Revoke npm and registry publishing tokens, prioritising any that can publish without a second factor.

Near term: this week.

Rotate credentials in the order the worm collects them: registry tokens, then GitHub tokens, then cloud credentials, then SSH keys, Kubernetes configs and Terraform credentials [1]. Review your GitHub organisation for repositories created in that window, and for any whose description carries the campaign’s marker string [1]. Audit which of your CI workflows can publish to a public registry, and review trusted publisher and OIDC configuration [2].

Structural: this quarter.

Build a secrets inventory before you need one: repositories, CI systems, environment variables and developer machines. The recurring lesson across this campaign’s waves is that the organisations who recover quickly are the ones who already knew what they had and where. Move CI to short-lived, scoped credentials so a theft has an expiry. Consider dependency allowlisting and pinning, and adopt npm 12 or newer so install hooks do not execute by default [1][2].

Checklist

  • Search lockfiles, build logs and CI caches for affected versions around August 4, 2026, including versions since replaced.
  • Treat any host or runner that installed one as compromised; rebuild it.
  • Revoke registry publishing tokens, starting with any that skip two-factor.
  • Rotate GitHub, cloud, SSH, Kubernetes and Terraform credentials.
  • Review new public repositories in your GitHub organisation.
  • Audit which CI workflows can publish, and review OIDC and trusted publishers.
  • Move to npm 12 or newer so install hooks do not run by default.
  • Build a secrets inventory covering repos, CI, env vars and laptops.
  • Pin and allowlist dependencies where your tooling supports it.

What this campaign keeps proving

Each wave of this worm lands the same point from a different angle. In earlier waves the entry was a compromised CI pipeline or a poisoned release process. This time it was one maintainer’s account. The constant is not the entry. It is that a developer machine and a build runner hold enough credentials to reach everything, and almost nobody treats them like production systems.

The provenance detail is the part that should stay with you. The industry spent years building signing infrastructure to answer the question “did this come from where it claims?” This attack answered that question honestly and was malicious anyway. Attestation of origin was always going to be necessary. It was never going to be sufficient, and now there is a two-billion-install example of why.

FREQUENTLY ASKED

I installed an affected version. Is removing it enough?
No. JFrog's guidance is explicit on this point, if an affected version ran in your environment, removing the package does not undo what already executed. The install-time payload harvests credentials the moment it runs. You have to treat every secret reachable from that machine or CI runner as exposed and rotate it.
How did malware get valid npm provenance? Isn't that the point of provenance?
Provenance proves a package was built by a particular workflow, not that the workflow built something safe. The attacker pushed malicious code into the repository and let the project's own trusted GitHub Actions pipeline publish it. The signature is truthful. This really was built by that workflow. It just says nothing about whether the source was clean.
Does npm's --ignore-scripts flag protect me?
Partially, and less than you would hope. Install hooks are one of several execution paths this malware family has used; earlier waves also used import-time execution and Python startup files to run without an install script. JFrog notes that on npm 12 and newer, preinstall hooks do not run by default, which helps, but only if you are on that version.
What should we rotate, and in what order?
npm and registry publishing tokens first, because those are what the worm uses to spread. Then GitHub tokens, then cloud credentials, then SSH keys and Kubernetes configs. Singapore's CSA also lists Terraform credentials. Revoke rather than rotate where you can, a rotated token that was already used to publish does not undo the publish.
How would we even know if we were hit?
Check whether any affected version appears in your lockfiles, build logs or CI caches for the period around August 4, 2026, not just your current dependency tree, since a bad version may have been installed and then replaced. CSA also suggests looking for GitHub repositories in your organisation whose description contains the string the campaign leaves behind.

REFERENCES

  1. [1]Ongoing npm Supply Chain Attack Affecting Keyv and Related Packages ("Shai-Hulud" Worm)Cyber Security Agency of Singapore (CSA) · Published August 6, 2026 · Accessed August 14, 2026PRIMARY
  2. [2]Shai-Hulud is back: keyv and 400+ packages hijackedJFrog Security Research · Published August 4, 2026 · Accessed August 14, 2026VENDOR RESEARCH
  3. [3]Keyv and friends compromised in npm supply chain attackAikido Security · Published August 4, 2026 · Accessed August 14, 2026VENDOR RESEARCH

BEFORE YOU GO

Was this useful?

Tell me what you'd change, what was unclear, or what you'd want covered next. Replies shape what gets written.

SEND FEEDBACK ↗

The newsletter

Follow along

Notes between posts, and whatever I'm breaking in the lab.

LINKEDIN ↗