by datastudy.nl

Field notes for teams tracking critical CVEs and major incidents

Engineering

npm supply chain attack evades install defenses at runtime

An npm supply chain attack hides malware in runtime methods, bypassing npm v12 install-script blocks. Ten packages and 7 million downloads are affected.

Bar chart of weekly downloads across ten malicious npm packages in the indexed-btree campaign. indexed-btree leads at 2,000,000, btree-core at 1,951,274, btree-leaderboard at 493,685, btree-range-store at 468,092, ordered-kv-index at 448,184, sliding-score-window at 448,024, btree-time-index at 425,312, priority-slot-queue at 402,860, btree-lru-cache at 372,185, neighbor-key-map at 366,019. Combined total approximately 7 million downloads.
Weekly downloads for the ten malicious npm packages in the indexed-btree runtime evasion campaign. Source: Checkmarx via BleepingComputer. Data Today benchmark.

When GitHub announced npm v12's security measures in June 2026, the pitch was straightforward: block lifecycle scripts like preinstall, install, and postinstall unless a developer explicitly approves them. For an ecosystem battered by supply chain attacks since late 2025, it was a meaningful step. But the latest npm supply chain attack campaign, disclosed by Checkmarx researchers and reported by BleepingComputer on September 20, 2026, shows what happens when attackers stop fighting the fence and walk around it.

The campaign centers on a package called indexed-btree, which impersonates the legitimate sorted-btree library and had accumulated 2 million weekly downloads before discovery. Instead of planting malicious code in an install script, the attackers hid a loader inside the package's BTree.prototype.set() method. That function fires at runtime, every time an application calls it with a specific key value. npm v12's approval mechanisms never trigger because installation looks completely clean.

An npm supply chain attack that hides in runtime code has exposed ten packages with a combined 7 million downloads, proving that install-time-only defenses are no longer sufficient.

How does the malware hide inside a package's runtime code?

The evasion technique is simple in concept and surgical in execution. A typical npm supply chain attack plants a payload in a lifecycle hook that runs during npm install, which means tools scanning for suspicious install behavior can catch it. npm v12 specifically targets this vector by blocking these scripts unless explicitly approved.

The indexed-btree package contains no lifecycle scripts at all. The malicious loader sits inside BTree.prototype.set(), which Checkmarx describes as "the main function that every user would call constantly." When an application calls set() with a specific key value, the method loads sharedLoad.min.js, an obfuscated first-stage payload. Because the trigger is a normal API call at runtime, not an installation event, it bypasses npm v12's approval gates entirely.

Checkmarx notes that this approach is "a well-built way to sneak past standard taint-analysis tools and most static scanners." The package also went to considerable lengths to appear legitimate. The threat actors built a convincing GitHub repository with a populated commit history and a curated developer account. If you reviewed the package on npm or GitHub, nothing about it would scream malware.

This is the second major campaign in 2026 to use runtime triggering. The node-ipc supply chain attack documented by StepSecurity used an Immediately Invoked Function Expression appended after the final module.exports line. That payload fired on require() with no method call needed, no lifecycle hooks, and no install-time trigger. The pattern is clear: attackers have learned that install scripts are the most watched door, so they are using a different one.

What happens after the payload fires?

Once the first-stage loader executes, the malware follows a multi-stage chain designed for stealth and remote control.

First, it collects system information: architecture, hostname, CPU, memory, and uptime. That data gets exfiltrated through hardcoded Slack and Telegram channels, which is a clever choice because those domains are almost always allowlisted in corporate egress filters.

Then the malware polls an Ethereum smart contract on the Sepolia test network for command-and-control instructions. Using a blockchain testnet for C2 means the attacker does not need a persistent server that could be blocked or taken down. The malware uses X25519 key exchange to derive an AES key, which decrypts a second-stage payload stored in the contract. That second stage can presumably do far more than reconnaissance.

When the operators decide to end the campaign, the malware can delete its own files and remove the malicious trigger from the package code, wiping its traces. If a forensics team examines the package after cleanup, it appears clean.

The attackers also hold a wallet with 109 ETH, though Checkmarx does not confirm whether those funds came from this campaign's activities.

How many packages and downloads are in scope?

Checkmarx identified ten packages in total. indexed-btree is the headliner with its 2 million weekly downloads, but the other nine are not small. Here is the full list, sorted by download count:

  • indexed-btree: approximately 2,000,000 downloads
  • btree-core: 1,951,274 downloads
  • btree-leaderboard: 493,685 downloads
  • btree-range-store: 468,092 downloads
  • ordered-kv-index: 448,184 downloads
  • sliding-score-window: 448,024 downloads
  • btree-time-index: 425,312 downloads
  • priority-slot-queue: 402,860 downloads
  • btree-lru-cache: 372,185 downloads
  • neighbor-key-map: 366,019 downloads
Bar chart showing weekly downloads for ten malicious npm packages. indexed-btree at 2,000,000, btree-core at 1,951,274, btree-leaderboard at 493,685, btree-range-store at 468,092, ordered-kv-index at 448,184, sliding-score-window at 448,024, btree-time-index at 425,312, priority-slot-queue at 402,860, btree-lru-cache at 372,185, neighbor-key-map at 366,019. Combined total approximately 7 million.
Weekly downloads for the ten malicious npm packages in the indexed-btree runtime evasion campaign. indexed-btree leads with approximately 2,000,000 and btree-core follows with 1,951,274. Source: Checkmarx via BleepingComputer. Data Today benchmark.

The combined download total across all ten packages is approximately 7 million. Checkmarx has coordinated with npm to remove all ten from the registry, but any environment that already installed them remains compromised. The download numbers tell you the blast radius: these packages were not sitting in a dark corner of npm. They were being pulled into real applications, likely by developers who searched for btree or sorted-map utilities and picked the top results.

Why do install-time scanners miss this entire class of attack?

The gap is structural. npm v12's security model, like most supply chain scanners, focuses on the installation phase. It asks whether a package runs scripts during npm install and blocks them unless approved. That model assumes malicious code wants to run at install time, which was a reasonable assumption through 2025 when most npm supply chain attacks used postinstall hooks.

Runtime evasion breaks that assumption. A package with no lifecycle scripts passes install-time checks by default. The malicious code only executes when the application imports the package and calls its API. By that point, the package has already been installed, committed to a lockfile, and potentially deployed to production.

Microsoft Threat Intelligence documented a related pattern in May 2026 with 33 malicious npm packages that used dependency confusion. Those packages did use postinstall hooks, but Microsoft's analysis noted a RECON_ONLY flag that could be toggled server-side for full exploitation later. The two-phase design, recon now and exploit later, is the same architecture the indexed-btree campaign uses. The difference is that indexed-btree moved the trigger from install time to runtime, making it invisible to the exact defenses GitHub built to stop the earlier campaign.

Splunk's analysis of npm supply chain attacks puts the detection problem plainly: the code executes before most runtime security controls activate. That was true when the trigger was install time. Now that the trigger is runtime, the calculus flips. Install-time controls activate and see nothing. Runtime controls, if they exist at all, may catch it, but most teams do not run behavioral analysis on their npm dependencies in production.

What should your team do about it today?

If you have installed any of the ten packages listed above, treat this as an active compromise. Checkmarx and the broader consensus among incident responders point to three immediate steps:

  • Rotate all secrets. Environment variables, API keys, cloud credentials, tokens in CI/CD pipelines. The malware collects environment variables and system context. Assume anything in the environment where these packages ran is exposed.
  • Restore development environments from a known safe backup. Do not just remove the packages and continue. The malware can self-delete and wipe traces, which means the absence of evidence is not evidence of safety.
  • Audit your dependency tree for runtime-triggered code. This is the longer-term work. You need to examine what your dependencies do when their APIs are called, not just what they do when they are installed.

For teams that have not been affected, the defensive shift is clear. Install-time scanning is necessary but insufficient. You need runtime behavioral analysis that watches what your dependencies actually do when your application calls them. Tools that only look at package.json scripts and lifecycle hooks will miss this entire class of attack.

This campaign also reinforces why the ChainDrop npm worm that hit 1,300 packages and the Keyv npm worm that poisoned 444 packages were warnings, not isolated events. The npm ecosystem is large, permissionless by default, and full of packages that look legitimate. Every defensive improvement at the install layer pushes attackers to find the next layer, and the next layer is your runtime.

The fight moves from install time to runtime

npm v12 was a good step. It closes the easiest, most-abused attack vector in the npm ecosystem. But indexed-btree proves that attackers adapt in weeks, not years. The moment install scripts became gated, they moved the trigger into the code your application actually calls. Your defenses need to follow them there.

The packages are removed. The infections are not. If any of these ten packages are in your lockfile, the cleanup starts today, not with an npm uninstall, but with a full secret rotation and environment rebuild. And if your supply chain security strategy ends at install time, it now has a gap wide enough for 7 million downloads to walk through.

Sources