
A self-propagating worm tore through the npm registry on Tuesday morning, trojanizing the widely used keyv and cacheable caching libraries and spreading to more than 400 packages within hours. Wiz Research, Socket, and Microsoft Threat Intelligence are all tracking the campaign, which is still live at the time of writing.
The scale is bad enough — keyv alone pulls roughly 154 million weekly downloads. But the detail incident responders need to read first is different: this malware is waiting for you to clean up.
Revoking the token is what pulls the trigger
Socket's researchers found a host-level dead-man's switch planted alongside the credential theft. The payload writes the stolen GitHub token and an attacker-supplied handler command to ~/.config/gh-token-monitor/, then installs itself as a macOS LaunchAgent (com.user.gh-token-monitor) or a Linux systemd user service with loginctl enable-linger so it survives logout.
A watcher script at ~/.local/bin/gh-token-monitor.sh then polls the GitHub API with that stolen token every 60 seconds. When the token stops working — an HTTP 4xx response, which is precisely what happens the second a security team revokes it — the script calls eval on the remote-supplied handler, deletes its own state, and exits. It also self-clears after 24 hours.
Rotating credentials is step one of every supply chain playbook. Here, it is the detonator. Socket's advice is blunt: hunt for and remove the switch before rotating anything.
The disguise is deliberate. The systemd unit describes itself as a "GitHub Token Validity Monitor" and reads like a developer convenience at a glance. As Socket notes, no linter or release tool installs a background service that watches for its own credentials to be revoked.
How it started
According to Wiz, the attacker took over a GitHub maintainer account around 09:00 UTC on August 4 and first pushed IDE persistence payloads into the keyv repository. Malicious versions followed shortly after.
[email protected] landed at 09:35 UTC carrying a preinstall hook — a script npm runs automatically before installation completes. Socket's scanner flagged it about six minutes later. By 09:38, a package outside both namespaces, @thiennq/[email protected], had been poisoned, confirming the worm had already jumped accounts. The cacheable family went out in a burst between 10:09 and 10:14, including [email protected], [email protected], [email protected], and [email protected].
Most victims never installed any of these on purpose. A typical chain runs ESLint → file-entry-cache → flat-cache → keyv. Wiz's prevalence data puts file-entry-cache, flat-cache, and keyv in roughly 46% of the cloud and code environments it measured.
Notably, the shipped library code was untouched. The dist/ output in [email protected] is byte-identical to the clean release candidate. Only package.json and two new files changed — so the package works normally while the host is already owned.
Bun as an evasion layer
Stage one, setup.mjs, downloads a standalone Bun runtime (v1.3.13) straight from GitHub releases, with no checksum or signature check, then uses it to execute the second stage. Running the payload under Bun rather than Node sidesteps tooling that only watches Node processes. Wiz has added the Bun/1.3.13 user-agent to its IOC list.
Stage two is Math_Symbol.js, a ~728 KB bundle with strings hidden behind polymorphic basE91 encoding. It hits AWS instance metadata at 169.254.169.254, reads Vault tokens, Kubernetes service account tokens, GCP and Azure secrets, npm tokens, and runs a TruffleHog-style regex sweep for anything key-shaped on disk. Stolen npm credentials then feed OIDC trusted publishing, letting the worm repackage and republish whatever the token can reach.
Two further twists stand out. Wiz found the malware pulls its command-and-control domains from an Ethereum smart contract via eth_call instead of hardcoding them — on-chain history shows it was narrowed down to npm-cache[.]com. And the source repository plants autostart hooks in .claude/settings.json and .vscode/tasks.json, meaning a developer or an AI coding agent that merely opens the cloned repo gets popped. No npm install required.
Wiz assesses the payload as a descendant of the "Mini" Shai-Hulud family, sharing traits with the TeamPCP and AntV campaigns. Exfiltration repos carry the description "Shai-Hulud: Here We Go Again," alongside a new taunt string threatening third-party production servers. Microsoft says a single actor is behind all of it, using multiple stolen tokens, and detects the payload as Trojan:npm/MalBun.A.
The provenance problem, again
[email protected] shipped with a valid attestation. Not forged — genuinely valid, because the legitimate release workflow built already-trojanized source. Socket's summary is the line worth pinning above the desk: provenance attests to build integrity, not source integrity.
This is the same failure mode Cyber Kendra documented when TeamPCP's worm hit 42 TanStack packages in May with valid SLSA Build Level 3 attestations, and again when Red Hat Cloud Services npm packages were backdoored through the project's own OIDC workflow in June. The lineage runs back through the original Shai-Hulud wave that swallowed CrowdStrike's packages in September 2025.
What to do now
Developers: pin every affected package to the version immediately prior and rebuild lockfiles by integrity hash. No caret or tilde ranges, no npm update — the maintainer account is still considered compromised. Blocking the entire keyv, @keyv, and cacheable scopes at your registry proxy is the safer play.
Security teams: remove the dead-man's switch first. Check for gh-token-monitor.sh, the LaunchAgent plist, the systemd user unit, /tmp/bun-dl-* directories, Math_Symbol.js or math_init.js on disk, and the .claude/.vscode autostart hooks. Assume an equivalent monitor exists for the npm token.
Only then revoke — not merely rotate — npm and GitHub tokens, followed by AWS, GCP, Azure, Vault, Kubernetes, and CI secrets. Finish by auditing npm accounts for unexpected versions published today and GitHub for repositories you did not create.