The Package Everyone Trusts Turned Hostile. Upwind Security Found It First.

Upwind Security was first to report a supply chain compromise in keyv, one of the most heavily depended-upon packages in the npm registry, after identifying a malicious release that executes attacker-controlled code at install time.

The compromised release introduces a preinstall lifecycle hook. That hook fires during installation, before any developer has the opportunity to inspect what arrived in node_modules. It runs an obfuscated loader, which retrieves the Bun JavaScript runtime from GitHub Releases and uses it to launch a bundled payload built for credential collection.

report from Upwind

What the Payload Collects

Upwind’s analysis identifies the collection scope across the credential types that carry the most weight in a modern build environment: AWS keys, GitHub tokens, npm registry credentials, and HashiCorp Vault tokens. The harvesting runs on developer workstations and inside CI/CD environments without distinction.

Upwind’s Combined Incident Report scores the campaign at 92 overall. Impact sits at 93, evidence at 95, sophistication at 88. The classification is malicious rather than suspicious, which reflects confirmed payload behavior rather than heuristic detection.

The report is unambiguous about what an install means. In Upwind’s words, “Any machine that ran npm install against an affected package version has already executed attacker-controlled code with the privileges of the installing user.”

Eight Packages, Multiple Namespaces

keyv was the entry point rather than the boundary. Upwind documented identical malicious payloads across eight releases:

The spread covers the entire @cacheable ecosystem alongside keyv, flat-cache, and cache-manager. On how a single actor reached across separate maintainer namespaces simultaneously, Upwind’s report describes the breadth as suggesting “either a coordinated multi-account compromise or a single threat actor with access to the @cacheable, keyv, and related ecosystems.”

Downstream exposure extends past direct consumers. Upwind names ESLint users specifically, since flat-cache and file-entry-cache sit inside that toolchain, along with any project depending on keyv, flat-cache, or cache-manager.

Indicators of Compromise

Three artifacts identify an affected installation. The files setup.mjs and Math_Symbol.js appear in package contents where no legitimate caching library would place them. The command node setup.mjs appears in the manifest as the preinstall entry.

Upwind characterizes the install-time component as an opaque script of roughly 30KB that loads a 728KB payload. The size gap matters. The visible portion is small enough to survive a quick glance at a diff. The code that actually executes arrives separately.

Four techniques appear in Upwind’s assessment: preinstall hook abuse, obfuscated payload delivery, patch-version camouflage, and credential harvesting at install time. Patch-version camouflage deserves particular attention. Several poisoned releases sit a single increment above a clean version, which is exactly the kind of bump automated dependency tooling approves without human review.

Upwind lists the likely exfiltration set as “environment variables, AWS/cloud credentials, SSH keys, and system reconnaissance data.”

Remediation

Upwind’s guidance for affected teams follows four steps.

Pin or downgrade affected packages immediately. Lock them to the last known-good version in the lockfile, then block compromised versions at the registry or firewall level.

Rotate all credentials on exposed systems. Any machine or CI/CD runner that installed an affected version should be treated as compromised. AWS keys, SSH keys, API tokens, and environment secrets all fall in scope.

Disable npm install scripts in CI/CD. Adding the ignore-scripts flag to install invocations in pipelines prevents preinstall and postinstall hooks from executing without explicit review.

Audit installed packages for malicious files. Scanning node_modules for setup.mjs or Math_Symbol.js surfaces the compromise directly, since neither file is a legitimate component of any caching library.

Upwind additionally advises reviewing lockfiles and SBOMs for the affected release. A version resolved and recorded weeks ago will reintroduce the package on the next clean build regardless of what the registry currently serves.

A Second Campaign, Same Method

Upwind documented a parallel npm compromise using the same delivery chain against a different ecosystem. Eight packages across the Qlik and nebula.js scopes were backdoored: @nebula.js/[email protected], @qlik/[email protected], @nebula.js/[email protected], @nebula.js/[email protected], @qlik/[email protected], @nebula.js/[email protected], @nebula.js/[email protected], and @nebula.js/[email protected].

report from Upwind

That report scores 94 overall, with impact at 95 and evidence at 97. A preinstall hook fires setup.mjs, which fingerprints host operating system and architecture, downloads Bun v1.3.13 from GitHub when absent, and executes a 727KB payload named math_init.js with full user-level filesystem and network access. Upwind’s read on the access required is that the breadth of affected packages “suggests the attacker had write access to the entire Qlik/nebula.js npm organization.”

The Reach Problem

keyv draws approximately 154 million weekly downloads. It operates as foundational infrastructure across thousands of JavaScript projects, the overwhelming majority of which never reference it directly. It arrives as a dependency of a dependency.

That structure is what separates this incident from the compromise of a package developers consciously selected. Exposure is determined by transitive resolution, not by adoption. Upwind’s closing framing holds: highly trusted, high-volume dependencies remain prime targets for supply chain attackers, and a single malicious release can carry ecosystem-wide consequences.

Leave a Reply

Your email address will not be published. Required fields are marked *

11 − 2 =