Skip to content

Back to the catalog

NPM Malware Compromises keyv and cacheable with 500M+ Weekly Downloads and Spreads to Hundreds of Packages | Blog | Endor Labs
endorlabs.comadvisory

NPM Malware Compromises keyv and cacheable with 500M+ Weekly Downloads and Spreads to Hundreds of Packages | Blog | Endor Labs

Endor Labs' active-tracking advisory on an npm supply-chain worm that compromised keyv, flat-cache, and file-entry-cache (500M+ combined weekly downloads) via the jaredwray/Cacheable GitHub Actions OIDC trusted-publishing pipeline, injecting a preinstall hook (setup.mjs) that fetches a legitimate Bun binary to run an obfuscated ~727KB stage-2 payload (Math_Symbol.js/math_init.js) stealing AWS, npm, GitHub, and Vault tokens. The stolen tokens were used to self-propagate the same payload to 384+ packages and 1,136+ versions across unrelated maintainer accounts, including scopes belonging to companies like ServiceTitan, Qlik, and Picsart. The post includes IOCs (file hashes), affected package/version lists, and mitigation guidance (lockfile/CI log checks, --ignore-scripts).

TL;DR A self-propagating npm worm compromised keyv, flat-cache, and file-entry-cache (500M+ weekly downloads) via stolen publishing tokens and a Bun-executed credential-stealing preinstall payload, spreading to 384+ packages.
Document

What it is

An initial advisory (August 4, 2026) on an active, worming npm credential-stealer seeded in the jaredwray/Cacheable ecosystem and now republished across hundreds of unrelated packages. This document carries the operational details the summary omits: detection, mitigation, and indicators.

How it works

Every malicious version injects "scripts": { "preinstall": "node setup.mjs" }, so the payload runs at npm install time — including for transitive and dev-only dependencies. setup.mjs is an obfuscated dropper that downloads an unmodified Bun 1.3.13 binary from official GitHub releases, then uses Bun to run a ~727 KB obfuscated stage-2 payload shipped inside the package (Math_Symbol.js in the first wave, math_init.js later — the two files are byte-identical). Bringing its own runtime means the only stage-1 network fetch is a legitimate Bun release, with no attacker download domain to block. Stage 2 harvests AWS credentials, npm tokens, GitHub tokens (including ghs_ Actions/App tokens), and HashiCorp Vault tokens, then self-propagates by publishing new malicious versions with stolen tokens — mostly CI/service-account tokens harvested from build runners that had themselves installed a poisoned dependency.

Preconditions / Affected

  • Any npm install (without --ignore-scripts) that resolves an affected version.
  • flat-cache and file-entry-cache sit in the ESLint dependency chain, so many projects pull them transitively even as dev dependencies — this does not reduce risk.
  • First-wave tarballs carry valid npm signatures and SLSA provenance (published through the legitimate OIDC pipeline); later republishes under stolen tokens have no provenance.
  • Abused scopes include @servicetitan/*, @ornikar/*, @onereach/*, @or-sdk/*, @qlik/*, @nebula.js/*, @hubsync/*, @picsart/*, @arv-bedrock/*, @adminide-stack/* — via stolen tokens, not company-system compromise.

What to do

  • Do not trust "latest looks clean." Many bad versions have been unpublished, so a current lockfile resolving to a safe version does not mean you never installed a poisoned one. Audit historical lockfiles and CI logs.
  • Pin/force safe versions with overrides for keyv, flat-cache, file-entry-cache.
  • Install with --ignore-scripts where feasible to neutralize the preinstall hook.
  • Hunt for setup.mjs, a preinstall running node setup.mjs, bun-dl-* artifacts, and the stage-2 filenames.
  • Rotate any AWS, npm, GitHub, and Vault credentials reachable from affected build environments.

Indicators (SHA-256):

9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc  Math_Symbol.js / math_init.js
54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668  setup.mjs
fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb  setup.mjs

Caveats

Point-in-time snapshot for August 4, 2026. The verified 384 packages / 1,136 versions are explicitly a floor — versions are being unpublished and new ones keep appearing. Payload analysis is described as "initial"; a fuller technical write-up of the loader and worming behavior is promised in a follow-up.

Organisations named here

  • math_init.js
  • Math_Symbol.js
  • ServiceTitan
  • setup.mjs

Technologies named here

  • Endor Labs
  • keyv
  • Bun 1.3.13
  • flat-cache
  • file-entry-cache
  • @servicetitan/*
  • @ornikar/*
  • @onereach/*
  • @or-sdk/*
  • @qlik/*
  • @nebula.js/*
  • @hubsync/*
  • @picsart/*
  • @arv-bedrock/*
  • @adminide-stack/*
  • 384 verified packages / 1,136 malicious versions
  • npm
  • Bun
  • GitHub Actions
  • OIDC
  • AWS
  • Vault
  • cacheable
  • SLSA

Techniques named here

  • Malicious npm preinstall script
  • Trusted publishing pipeline abuse
  • Stolen npm publishing token abuse
  • Living-off-legitimate-binary execution
  • Credential theft / stealer
  • Self-propagation / worming via npm publish

Threat actors named here

  • npm keyv/cacheable supply chain compromise campaign