{"slug": "keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook", "title": "keyv 6.0.0 Compromised With an Install Hook and an IDE Auto-Run Hook", "summary": "The npm package keyv published version 6.0.0 on 4 August 2026 with a preinstall script that executes an obfuscated dropper, compromising any developer who installed it directly or as a transitive dependency. The same attacker also committed .claude/settings.json and .vscode/tasks.json to the repository, which run code when the project is opened in VS Code or Claude Code, affecting developers who merely cloned the source. The malicious version remains under the latest dist-tag, and the repository's main branch still contains the hook files.", "body_md": "# keyv 6.0.0 Compromised With an Install Hook and an IDE Auto-Run Hook\n\n### Table of Contents\n\nThe npm package `keyv`\n\npublished version 6.0.0 on 4 August 2026 carrying a `preinstall`\n\nscript that runs an obfuscated dropper. Anyone who installed that version, directly or as a transitive dependency, executed attacker code before a single line of their own application ran. The release is still served under the `latest`\n\ndist-tag at the time of writing, so a plain `npm install keyv`\n\nresolves to the malicious version.\n\nThe second trigger matters more. The same attacker committed `.claude/settings.json`\n\nand `.vscode/tasks.json`\n\ninto the repository, both wired to run a local script the moment you open the project. Cloning the repository is harmless. Opening it in VS Code or starting a Claude Code session inside it is not. Developers who never installed the package, and who pulled the source only to read it after the reports appeared, are inside the blast radius. With 199 forks and 3,167 stars, that is a separate exposed population from the install path.\n\n## TL;DR\n\non npm carries[[email protected]](/cdn-cgi/l/email-protection)`\"preinstall\": \"node setup.mjs\"`\n\n. The published manifest still had it when this post was written, with no deprecation applied and no 6.0.1.- The repository ships\n`.claude/settings.json`\n\nwith a`SessionStart`\n\nhook and`.vscode/tasks.json`\n\nwith a`folderOpen`\n\ntask. Both execute on open, before any user action. Both files are still present on`main`\n\n. - The two config files are cross-wired. The Claude hook runs the script in\n`.vscode/`\n\n, and the VS Code task runs the script in`.claude/`\n\n, so neither directory looks self-contained on a quick read. - The commit that added those five hook files is\n**cryptographically verified by GitHub** and has its author field set to`github-actions[bot]`\n\n. Signature status is not a reliable authorship signal here. - Stage one downloads official Bun builds from\n`github.com/oven-sh/bun`\n\nto get a runtime, so there is no attacker domain in the install-time network traffic. - A later commit copied both payload files into all 19\n`@keyv/*`\n\nworkspace packages. Any release cut from`main`\n\nin its current state would ship them.\n\n## What was published to npm\n\nThe manifest npm serves for\n\ncarries the install hook:[[email protected]](/cdn-cgi/l/email-protection)\n\nThe tarball resolves to `sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==`\n\n. The publish ran through GitHub Actions as a trusted publisher over OIDC, so the release carries valid SLSA provenance pointing at `refs/tags/v6.0.0`\n\nand the real release workflow.\n\nThe provenance is genuine, and it proves only that poisoned source was built by the real pipeline. Valid OIDC attestation has not been exonerating in several recent npm compromises and should not be read as exonerating here. Since the payload executed inside a job holding npm publish rights and a `GITHUB_TOKEN`\n\n, the CI environment should also be treated as compromised.\n\nThe [package manifest in the repository](https://github.com/jaredwray/keyv/blob/1f79edd843e2a99ba1634874bf5e5b73cbefb74c/core/keyv/package.json) shows how the payload reached the tarball:\n\nThe library code under `dist/`\n\nwas left alone. Two files were added to the `files`\n\nallowlist and one line was added to `scripts`\n\n. Nothing about the package’s runtime behaviour changed, which is what keeps a diff against the previous release candidate small enough to skim past.\n\n## The trigger that does not need an install\n\nTwo config files in the repository run code when a developer opens the project.\n\nThe attacker picked the label `Environment Setup`\n\nto survive a glance, and crossed the two files over each other for the same reason. Open `.claude/settings.json`\n\n, go looking for `.claude/setup.mjs`\n\n, and you find a reference pointing out of the directory instead. The same trick applies in reverse. Reading either file on its own never shows you a self-contained loop.\n\nBoth `setup.mjs`\n\ncopies in the repository are 11,017 bytes and `.claude/math_init.js`\n\nis 727,680 bytes, matching `Math_Symbol.js`\n\nin the npm tarball. The install path and the open-the-repo path converge on the same code.\n\nThis is the technique covered in [config files that run code](/config-files-that-run-code), and its agent-directed form appears in the [Miasma worm’s targeting of AI coding agents](/miasma-worm-ai-coding-agent-config-injection). Scanning a lockfile catches a poisoned dependency. It does not catch a poisoned `tasks.json`\n\nin a repository the developer just cloned.\n\n## What the commit signatures show\n\n[Commit d8c850c7](https://github.com/jaredwray/keyv/commit/d8c850c7800e) carried the five hook files. Its metadata is the most instructive artifact in the incident:\n\nThat commit shows a green verified badge. GitHub signs commits created through its API or web UI with its own key, and the caller supplies the author name and email as free text. A credential with write access is therefore enough to produce a verified commit attributed to a bot. The `Co-authored-by: claude`\n\ntrailer and the `chore: update config`\n\nsubject finish the camouflage, dressing the change as routine agent-assisted maintenance.\n\nThe rest of the incident commits were pushed over git and are unsigned, which stands out against the maintainer’s history where all 29 preceding commits from\n\nare signed and verified. Reading that contrast as the detection rule would be a mistake. The attacker demonstrated both capabilities in the same hour, and the commit that planted the IDE hooks is the one that looks most legitimate.[[email protected]](/cdn-cgi/l/email-protection)\n\nThis also constrains attribution rather than settling it. A spoofed author on an API-created commit is consistent with a stolen token or session and equally consistent with the account itself, and those are not distinguishable from outside the organisation. The accurate statement is that the `jaredwray`\n\naccount or a credential held by it is compromised. The maintainer should be treated as a victim pending their own account of events, and the operational response is identical either way.\n\n## The timeline\n\nThe same commit that added the payload also added `preinstall.test.ts`\n\n. The attacker [deleted it 21 minutes later](https://github.com/jaredwray/keyv/commit/f97eabcdd057105f1fce3f05d6c029dac3f2ac78) in a commit that touched nothing else. It was not a pre-existing guardrail anyone had to defeat. It reads as a test written alongside the new `preinstall`\n\nscript, then removed once it became an obvious pointer to the thing it covered.\n\n## How stage one gets a runtime\n\nThe stage-one analysis below comes from [issue #2047](https://github.com/jaredwray/keyv/issues/2047), an independent static review. SafeDep did not download or execute the payload files, so this is reproduced as a reported reconstruction rather than a confirmed finding.\n\n`setup.mjs`\n\nis obfuscator.io-style, using a hex string array with rotation. The recovered control flow runs `which bun`\n\n, and if Bun is absent it detects platform and architecture, downloads a matching build from `github.com/oven-sh/bun/releases/download/bun-v1.3.13/`\n\n, extracts it, then calls `execFileSync`\n\non `Math_Symbol.js`\n\nand deletes the downloaded runtime afterwards.\n\nThat Bun release exists, published 2026-04-20, and its asset naming matches the reconstruction, including the musl and baseline variants the report describes:\n\nTwo operational consequences follow. The only outbound host at install time is GitHub, so egress allowlists and reputation-based network monitoring will not fire on stage one, and hunting for a novel C2 domain in `setup.mjs`\n\nreturns nothing because there is not one. Absence of Bun is not a mitigation either, since the dropper fetches the runtime it needs and deletes it on the way out.\n\n## What stage two can reach\n\n`Math_Symbol.js`\n\nis a 727,680-byte Bun-compiled bundle. The same report describes a basE91 string table with per-module alphabets and lazy memoised decoding, and states that no plaintext HTTP or HTTPS URL survives anywhere in the blob, leaving the C2 host sealed.\n\nThe capability surface is recoverable without breaking that encoding, because property names read off external APIs and JSON responses cannot be renamed by an obfuscator. The reported set includes `ghtoken`\n\nand `npmtoken`\n\n, the `ghs_`\n\nGitHub App installation token prefix, a HashiCorp Vault auth response shape, `aws_access_key_id`\n\nand `aws_secret_access_key`\n\nconsistent with parsing `~/.aws/credentials`\n\n, a full TAR header field set implying in-process archive construction, and `dist`\n\n, `tarball`\n\nand `version`\n\nconsistent with npm registry interaction.\n\nNobody executed the payload and nobody decoded the string table, so there is no observed exfiltration and no recovered C2. Credential property names plus tar plus npm publish fields describe a release tool or a credential broker about as well as they describe an infostealer. The evidence establishes reach, not intent.\n\n## Blast radius beyond the main package\n\nEleven `@keyv/*`\n\nsiblings were published in the minutes before `keyv`\n\nitself, and the report states those are clean as published, with core `keyv`\n\nthe only package that shipped the hook.\n\nThe [09:39 commit](https://github.com/jaredwray/keyv/commit/174f6a55690b0812a69adef47260ba8714a9be48) then added `setup.mjs`\n\nand `Math_Symbol.js`\n\nto all 19 workspace packages across 57 files, covering `@keyv/redis`\n\n, `@keyv/mongo`\n\n, `@keyv/postgres`\n\n, `@keyv/sqlite`\n\n, the `compress-*`\n\nfamily and the `encrypt-*`\n\nfamily. That commit lands after the publish, so it reads as staging for a follow-up release. Any release cut from `main`\n\nin its current state ships the payload to the entire `@keyv`\n\nscope. Revoke the publish credential and disable the release workflow before cleaning the tree, so a routine release cannot fire in the meantime.\n\n## Clean versions\n\nhas no [[email protected]](/cdn-cgi/l/email-protection)`preinstall`\n\n, `install`\n\nor `postinstall`\n\nscript in its published manifest. Teams on 5.x are unaffected and should stay pinned there.\n\nFor anyone who needs the v6 API, the report notes that `6.0.0`\n\n’s `dist/index.{cjs,mjs,d.cts,d.mts}`\n\nare byte-identical to `6.0.0-rc.1`\n\n, making the release candidate a functionally complete v6 without the hook. The library code was never touched in this compromise, only the manifest and the two added files.\n\n## Indicators\n\n| Type | Value |\n|---|---|\n| Malicious package | (npm `latest` at time of writing) |\n| Tarball integrity | `sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==` |\nsha256 `setup.mjs` (29,918 B) | `54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668` |\nsha256 `Math_Symbol.js` (727,680 B) | `9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc` |\n| Payload commit | `ee2681a9b62f3637b0eb5133c36c864d3376cc5b` |\n| IDE and agent hook commit | `d8c850c7800e` (verified, author spoofed to `github-actions[bot]` ) |\n| Evidence-removal commit | `f97eabcdd057105f1fce3f05d6c029dac3f2ac78` |\n| Sibling staging commit | `174f6a55690b0812a69adef47260ba8714a9be48` |\n| Stage-two runtime source | `github.com/oven-sh/bun/releases/download/bun-v1.3.13/*` (legitimate Bun, abused) |\n| C2 host | Not recovered |\n\nThe two sha256 values are carried from the upstream reports and were not independently recomputed for this post.\n\n## Remediation\n\n**Do not install ** Pin to\n\n[[email protected]](/cdn-cgi/l/email-protection).\n\n`5.6.0`\n\nor to a 5.x already present in your lockfile. Resolving `latest`\n\nor accepting a caret range currently lands on the malicious version.**Do not open a clone of the repository in an IDE or coding agent.** Cloning is safe. Opening triggers the `folderOpen`\n\ntask and the `SessionStart`\n\nhook, and both files are still live on `main`\n\n.\n\nAudit for exposure across both paths:\n\nIf\n\nwas installed on a developer machine or in CI, treat it as credential exposure. Rotate npm tokens, GitHub tokens and App installation credentials, cloud keys and any Vault credentials reachable from the process environment. The property names recovered from stage two point at exactly that set.[[email protected]](/cdn-cgi/l/email-protection)\n\nYou can gate this class of release before it reaches a lockfile using [vet](https://github.com/safedep/vet), which evaluates install hooks and package behaviour as a CI policy check rather than an after-the-fact investigation.\n\n## Closing note\n\nValid SLSA provenance signed a malicious artifact here, because provenance records where a build came from and not whether its inputs deserved trust. A green verified badge attributed the IDE hooks to a bot, because GitHub signs what its API is told to sign. Both are signals a reviewer would normally lean on, and neither survived contact with this incident.\n\nThe attacker also shipped a trigger aimed at people who avoided installing anything, betting that developers would clone the repository to read the source once the compromise went public. Keep that in mind the next time you pull down a suspicious repo to look at it.\n\n- malware\n- npm\n- supply-chain\n- account-compromise\n- config-files\n\n### Author\n\n#### SafeDep Team\n\nsafedep.io\n\n### Share\n\n## The Latest from SafeDep blogs\n\nFollow for the latest updates and insights on open source security & engineering\n\n[ ](/joyfill-npm-blockchain-c2-supply-chain)\n\n## [ Joyfill npm Packages Compromised with Blockchain C2 Loader ](/joyfill-npm-blockchain-c2-supply-chain)\n\nMalicious beta versions of @joyfill/components and @joyfill/layouts published on July 28, 2026 carried the PolinRider blockchain dead drop loader inside their production bundles. The Tron-to-BSC C2...\n\n[ ](/malicious-copilot-mcp-apex-npm-macos-infostealer)\n\n## [ @copilot-mcp/apex: A macOS Infostealer Re-Published on npm After Takedown ](/malicious-copilot-mcp-apex-npm-macos-infostealer)\n\nThe npm security team removed the original @apexfdn/apex package for malicious code, and the operator re-published the same postinstall macOS infostealer as @copilot-mcp/apex about 11 hours later. It...\n\n[ ](/mrmustard-malicious-pypi-package)\n\n## [ mrmustard PyPI Package Trojanized to Steal Credentials ](/mrmustard-malicious-pypi-package)\n\nVersion 0.7.4 of Xanadu's mrmustard quantum computing library shipped to PyPI with no matching GitHub release. It carries a 258-line credential stealer that harvests SSH keys, AWS and Kubernetes...\n\n[ ](/asyncapi-generator-supply-chain-attack-miasma-rat)\n\n## [ AsyncAPI Packages Compromised with Miasma RAT ](/asyncapi-generator-supply-chain-attack-miasma-rat)\n\nFour @asyncapi npm packages were published with obfuscated malware on July 14, 2026 via compromised CI workflows. The payload downloads Miasma RAT, a credential stealer targeting browsers, SSH keys,...\n\n## Ship Code.\n\n## Not Malware.\n\nStart free with open source tools on your machine. Scale to a unified platform for your organization.", "url": "https://wpnews.pro/news/keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook", "canonical_source": "https://safedep.io/keyv-npm-supply-chain-compromise", "published_at": "2026-08-04 00:00:00+00:00", "updated_at": "2026-08-04 10:52:48.234380+00:00", "lang": "en", "topics": ["ai-tools", "ai-ethics"], "entities": ["keyv", "npm", "GitHub", "VS Code", "Claude Code", "jaredwray"], "alternates": {"html": "https://wpnews.pro/news/keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook", "markdown": "https://wpnews.pro/news/keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook.md", "text": "https://wpnews.pro/news/keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook.txt", "jsonld": "https://wpnews.pro/news/keyv-6-0-0-compromised-with-an-install-hook-and-an-ide-auto-run-hook.jsonld"}}