SINGAPORE, 7 AUG 2026 — Singapore's Cyber Security Agency issued an advisory on 6 August about a self-propagating worm loose in the npm registry. The instruction in it is not the usual one. Organisations running Node.js are told to review their dependencies and to treat credentials on affected systems as potentially compromised.
Not patch. Treat your credentials as already taken.
What happened
On 4 August the attackers reached the maintainer of keyv, a key-value storage library, and trojanised the monorepo behind it. Every subpackage in that repository was backdoored with a dropper.
The malware, which Elastic Security Labs calls CHAINDROP and CSA places in the Shai-Hulud family, steals developer credentials. It then uses the stolen npm credentials to publish infected patch releases of every other package the compromised maintainer can write to. This is the worming step that let a single account takeover spread.
| Package | Compromised version |
|---|---|
| keyv | 6.0.0 |
| flat-cache | 6.1.24 |
| file-entry-cache | 11.1.6 |
| cacheable-request | 13.0.20 |
| cacheable | 2.5.1 |
| @cacheable/memory | 2.2.1 |
| cache-manager | 7.2.10 |
| @cacheable/node-cache | 3.1.2 |
| @cacheable/utils | 2.5.1 |
| @cacheable/net | 2.1.1 |
| ecto | 5.0.1 |
The notable compromised packages listed in CSA advisory AD-2026-009 of 6 August 2026. This is the advisory's own selection, not the full list — CSA points to a vendor-maintained inventory for that. Version numbers are as CSA published them.
These are not exotic dependencies. They are caching libraries that sit several levels below anything a developer chose deliberately, pulled in by something that was pulled in by something else.
The headline numbers disagree, and the reason is instructive
Different responders published different counts in the same week, and a reader comparing them will conclude that somebody is wrong. Mostly they are counting different things.
| Source | What it counted | Figure |
|---|---|---|
| CSA (Singapore) | package versions on npm | over 1,300 |
| Elastic Security Labs | npm packages | 400+ |
| CSA (Singapore) | combined monthly downloads | 2 billion |
| Elastic Security Labs | monthly downloads of co-owned packages backdoored | over 1.3 billion |
A package can have many versions, so a count of package versions is expected to exceed a count of packages; the download figures differ because the scopes differ. RECATOOLS comparison of what each source states. We are not adjudicating between them and neither figure is presented here as the correct one.
A package is not a package version. One compromised maintainer publishing malicious patch releases across a portfolio generates many versions from a smaller number of packages, so the larger number is not an inflation of the smaller one. The same applies to the download totals, which depend on which set of packages is being summed.
The point is that the exposure is large — billions of monthly downloads, however you slice the numbers.
The technique worth understanding
The delivery chain was built to slip past obvious controls.
An infected package runs a dropper during npm's preinstall step, which means the code executes on installation rather than when anything is imported. A build that installs dependencies and then fails still ran it.
The loader is a small Node.js bootstrap that works on Linux, macOS and Windows. It uses an installed Bun runtime if one is present. If not, according to JFrog's analysis, it downloads Bun 1.3.13 from the official GitHub release, extracts it, runs the real payload, and removes the temporary copy afterwards.
This defeats several common controls. Detection tuned to Node.js sees a legitimate runtime doing the work. Egress filters that trust GitHub see a download from an official release page. And the temporary copy of the runtime is gone before anyone can inspect it.
JFrog describes the onward movement as using stolen npm credentials to publish infected patch releases of every writable package, and stolen GitHub credentials and GitHub Actions to infect repositories and steal further credentials. Results are exfiltrated through a dynamic HTTPS endpoint or through public repositories the attacker creates.
CSA's published indicators include an exfiltration domain, npm-cache[.]com, and three Ethereum RPC endpoints. Public blockchain infrastructure is difficult to take down and unremarkable in outbound traffic from a developer machine.
The reach, package by package
Aggregate download totals are easy to wave away as a big scary number. The per-package figures are harder to dismiss, because they show how ordinary the affected libraries are.
| Package | Monthly downloads |
|---|---|
| keyv | over 600 million |
| flat-cache | near 580 million |
| cacheable-request | over 137 million |
| cacheable | over 30 million |
| cache-manager | over 16 million |
Monthly download figures as stated by Elastic Security Labs for the month before the compromise. These describe the reach of the packages, not a number of compromised systems — a download is not an infection, and most of these installs pre-date the malicious versions.
A library pulling 600 million downloads a month is not something teams evaluate. It arrives as a dependency of a dependency, and it is present in build environments whose owners could not name it.
That is the property the attacker is buying. Not the library, but its position several levels down a tree that nobody reads.
Why one stolen token is enough
The mechanism JFrog describes explains why these campaigns compromise hundreds of packages, not just a handful.
A single npm token with broad write access turns every package owned by that publisher into the next wave. A maintainer who looks after eleven packages is eleven packages, and a maintainer with a large portfolio is a large portfolio. The attack does not need to scale; the publisher's own permissions do the scaling for it.
GitHub tokens extend the loop further, in JFrog's account, by exposing Actions secrets and by allowing execution hooks to be planted where developers are likely to trigger them. Credentials stolen from one victim become the means of reaching the next.
This is why the response cannot be a fixed list of bad versions. Any such list is just a snapshot of the campaign's output, not a boundary around it.
Why the credential instruction is the whole advisory
CSA leads with credentials because pinning a safe version does not undo an install that already happened.
If a compromised version was installed on a build agent, the credentials that agent held at that moment are the ones to rotate: npm tokens, GitHub tokens, cloud keys in the environment, anything the process could read. Upgrading the package closes the door. It does not retrieve what left through it.
This is the same lesson the regional toolchain listings carried at the start of the month, when every flaw CISA added in three days was a tool that builds or runs other software. Build systems hold credentials for other systems, which makes them valuable targets and patching an incomplete response.
What to do
The mechanical steps are the ones CSA sets out: review dependencies against the published list, apply the security updates, and check environments against the indicators of compromise. The advisory pairs each listed package with the corresponding security update, so the remediation detail does not have to be assembled from vendor blogs.
The judgement call is the credential rotation, because it is expensive and nobody wants to do it on a maybe. The question for your logs is narrow: did any build between 4 August and the day you pinned safe versions install one of the affected packages? If the logs cannot answer that, the answer is to rotate.
Lockfiles are worth a second look here. A lockfile pins what gets installed, which is protection — unless the lockfile was regenerated during the window, in which case it pinned the malicious version and will keep pinning it.
The same caution applies to container images and CI caches built during those days. Both preserve a dependency tree long after the registry has been cleaned up, and both are rebuilt on a schedule that has nothing to do with when an advisory lands.
What to watch
Two things.
The first is whether this lineage appears outside npm again. Security researchers have traced components of it to an April 2026 compromise in the Python ecosystem, which suggests tooling that is being reused rather than a one-off.
The second is whether registries change the preinstall behaviour that makes this work. Install-time script execution is a convenience with a well-known cost, and it is the mechanism every one of these campaigns depends on.