cd /news/ai-tools/keyv-6-0-0-compromised-with-an-insta… · home topics ai-tools article
[ARTICLE · art-85935] src=safedep.io ↗ pub= topic=ai-tools verified=true sentiment=↓ negative

keyv 6.0.0 Compromised With an Install Hook and an IDE Auto-Run Hook

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.

read11 min views1 publishedAug 4, 2026
keyv 6.0.0 Compromised With an Install Hook and an IDE Auto-Run Hook
Image: Safedep (auto-discovered)

Table of Contents

The npm package keyv

published version 6.0.0 on 4 August 2026 carrying a preinstall

script 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

dist-tag at the time of writing, so a plain npm install keyv

resolves to the malicious version.

The second trigger matters more. The same attacker committed .claude/settings.json

and .vscode/tasks.json

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

TL;DR #

on npm carries[email protected]"preinstall": "node setup.mjs" . The published manifest still had it when this post was written, with no deprecation applied and no 6.0.1.- The repository ships .claude/settings.json

with aSessionStart

hook and.vscode/tasks.json

with afolderOpen

task. Both execute on open, before any user action. Both files are still present onmain

. - The two config files are cross-wired. The Claude hook runs the script in .vscode/

, and the VS Code task runs the script in.claude/

, so neither directory looks self-contained on a quick read. - The commit that added those five hook files is cryptographically verified by GitHub and has its author field set togithub-actions[bot]

. Signature status is not a reliable authorship signal here. - Stage one downloads official Bun builds from github.com/oven-sh/bun

to 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 @keyv/*

workspace packages. Any release cut frommain

in its current state would ship them.

What was published to npm #

The manifest npm serves for

carries the install hook:[email protected] The tarball resolves to sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==

. 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

and the real release workflow.

The 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

, the CI environment should also be treated as compromised.

The package manifest in the repository shows how the payload reached the tarball:

The library code under dist/

was left alone. Two files were added to the files

allowlist and one line was added to scripts

. Nothing about the package’s runtime behaviour changed, which is what keeps a diff against the previous release candidate small enough to skim past.

The trigger that does not need an install #

Two config files in the repository run code when a developer opens the project.

The attacker picked the label Environment Setup

to survive a glance, and crossed the two files over each other for the same reason. Open .claude/settings.json

, go looking for .claude/setup.mjs

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

Both setup.mjs

copies in the repository are 11,017 bytes and .claude/math_init.js

is 727,680 bytes, matching Math_Symbol.js

in the npm tarball. The install path and the open-the-repo path converge on the same code.

This is the technique covered in config files that run code, and its agent-directed form appears in the Miasma worm’s targeting of AI coding agents. Scanning a lockfile catches a poisoned dependency. It does not catch a poisoned tasks.json

in a repository the developer just cloned.

What the commit signatures show #

Commit d8c850c7 carried the five hook files. Its metadata is the most instructive artifact in the incident:

That 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

trailer and the chore: update config

subject finish the camouflage, dressing the change as routine agent-assisted maintenance.

The 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

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

This 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

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

The timeline #

The same commit that added the payload also added preinstall.test.ts

. The attacker deleted it 21 minutes later 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

script, then removed once it became an obvious pointer to the thing it covered.

How stage one gets a runtime #

The stage-one analysis below comes from issue #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.

setup.mjs

is obfuscator.io-style, using a hex string array with rotation. The recovered control flow runs which bun

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

, extracts it, then calls execFileSync

on Math_Symbol.js

and deletes the downloaded runtime afterwards.

That Bun release exists, published 2026-04-20, and its asset naming matches the reconstruction, including the musl and baseline variants the report describes:

Two 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

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

What stage two can reach #

Math_Symbol.js

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

The 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

and npmtoken

, the ghs_

GitHub App installation token prefix, a HashiCorp Vault auth response shape, aws_access_key_id

and aws_secret_access_key

consistent with parsing ~/.aws/credentials

, a full TAR header field set implying in-process archive construction, and dist

, tarball

and version

consistent with npm registry interaction.

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

Blast radius beyond the main package #

Eleven @keyv/*

siblings were published in the minutes before keyv

itself, and the report states those are clean as published, with core keyv

the only package that shipped the hook.

The 09:39 commit then added setup.mjs

and Math_Symbol.js

to all 19 workspace packages across 57 files, covering @keyv/redis

, @keyv/mongo

, @keyv/postgres

, @keyv/sqlite

, the compress-*

family and the encrypt-*

family. That commit lands after the publish, so it reads as staging for a follow-up release. Any release cut from main

in its current state ships the payload to the entire @keyv

scope. Revoke the publish credential and disable the release workflow before cleaning the tree, so a routine release cannot fire in the meantime.

Clean versions #

has no [email protected]preinstall , install

or postinstall

script in its published manifest. Teams on 5.x are unaffected and should stay pinned there.

For anyone who needs the v6 API, the report notes that `6.0.0`

’s `dist/index.{cjs,mjs,d.cts,d.mts}`

are byte-identical to `6.0.0-rc.1`

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

Indicators #

Type Value
Malicious package (npm latest at time of writing)
Tarball integrity sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==
sha256 setup.mjs (29,918 B) 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668
sha256 Math_Symbol.js (727,680 B) 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc
Payload commit ee2681a9b62f3637b0eb5133c36c864d3376cc5b
IDE and agent hook commit d8c850c7800e (verified, author spoofed to github-actions[bot] )
Evidence-removal commit f97eabcdd057105f1fce3f05d6c029dac3f2ac78
Sibling staging commit 174f6a55690b0812a69adef47260ba8714a9be48
Stage-two runtime source github.com/oven-sh/bun/releases/download/bun-v1.3.13/* (legitimate Bun, abused)
C2 host Not recovered

The two sha256 values are carried from the upstream reports and were not independently recomputed for this post.

Remediation #

**Do not install ** Pin to

[email protected]. 5.6.0

or to a 5.x already present in your lockfile. Resolving latest

or 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

task and the SessionStart

hook, and both files are still live on main

.

Audit for exposure across both paths:

If

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

You can gate this class of release before it reaches a lockfile using vet, which evaluates install hooks and package behaviour as a CI policy check rather than an after-the-fact investigation.

Closing note #

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

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

  • malware
  • npm
- supply-chain
- account-compromise
- config-files

Author

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs #

Follow for the latest updates and insights on open source security & engineering

[ ](/joyfill-npm-blockchain-c2-supply-chain)

## [ Joyfill npm Packages Compromised with Blockchain C2  ](/joyfill-npm-blockchain-c2-supply-chain)

Malicious beta versions of @joyfill/components and @joyfill/layouts published on July 28, 2026 carried the PolinRider blockchain dead drop inside their production bundles. The Tron-to-BSC C2...

[ ](/malicious-copilot-mcp-apex-npm-macos-infostealer)

## [ @copilot-mcp/apex: A macOS Infostealer Re-Published on npm After Takedown ](/malicious-copilot-mcp-apex-npm-macos-infostealer)

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

[ ](/mrmustard-malicious-pypi-package)

## [ mrmustard PyPI Package Trojanized to Steal Credentials ](/mrmustard-malicious-pypi-package)

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

[ ](/asyncapi-generator-supply-chain-attack-miasma-rat)

## [ AsyncAPI Packages Compromised with Miasma RAT ](/asyncapi-generator-supply-chain-attack-miasma-rat)

Four @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,...

Ship Code. #

Not Malware. #

Start free with open source tools on your machine. Scale to a unified platform for your organization.

── more in #ai-tools 4 stories · sorted by recency
── more on @keyv 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/keyv-6-0-0-compromis…] indexed:0 read:11min 2026-08-04 ·