npm Supply Chain Malware Hides in Runtime Code

A counterfeit npm package skipped install scripts entirely and buried its loader inside a function that applications call every day.

The Package Passed. Govern What Executes.
  • September 21, 2026

The npm ecosystem spent years treating the install script as the danger. In July 2026 that assumption became a default setting. A counterfeit package called indexed-btree carries no install script at all.

The Wall That Went Up In July

GitHub announced npm v12 as generally available on July 8, 2026. Dependency lifecycle scripts, meaning preinstallinstall and postinstall, no longer run unless a project explicitly allows them. Implicit node-gyp builds stopped as well. Git dependencies and dependencies pulled from remote URLs are also no longer resolved by default.

Projects opt back in by running npm approve-scripts. Warnings had been appearing since npm 11.16.0 to give maintainers time to prepare.

That reasoning was sound. Install hooks ran attacker code the instant a developer typed a command, before anyone had read a line of the package.

A Package That Installs Perfectly Clean

indexed-btree impersonates sorted-btree, a real and widely used data structure library. The counterfeit reached roughly 2 million weekly downloads.

Installation triggers nothing. No hooks fire, no build step runs, no network connection opens. Static scanners find nothing to flag and taint analysis has nothing to follow, because at install time the package genuinely does nothing.

What is the indexed-btree npm attack? A supply chain attack campaign utilizing a fake npm package, which comes without any installation script. The loader used here resides in the write function in the library and gets executed only when an active program invokes the write function using the specified key. This technique evades the installation control mechanisms of npm v12 by executing beyond that stage.

The Loader Sits In The Function You Call Most

BTree.prototype.set() is the core write path of the library. Every application using the package calls it constantly, which makes it the last place a reviewer would look for a trigger.

The loader watches that call. It activates when a running application passes the key value 100.

What follows is deliberately quiet. The code spawns a detached Node.js child process running extended/sharedLoad.min.js, with stdio set to ignore and windowsHide enabled, so no console window appears and no output reaches the parent. Detaching the process also means it survives the application that started it.

sharedLoad.min.js holds the obfuscated first stage, protected with string-array encoding and self-checksumming array rotation.

Command And Control Runs On A Public Blockchain

The first phase gathers the host information, architecture, hostname, CPU, memory, and uptime which are sent using the hardcoded Slack and Telegram endpoints.

This is followed by the loader communicating with an Ethereum Sepolia testnet smart contract to obtain instructions. The loader generates an X25519 keypair, retrieves the operator’s public key from the smart contract, derives the secret, and then decrypts a second-stage payload using the generated AES key stored on the chain.

This approach eliminates all the common attack vectors. There are no domains controlled by the attackers that must remain up since they do not need to notify any registrar or hosting service about anything. Public blockchains cannot be brought down upon request and communicating with such a blockchain creates traffic similar to regular applications.

Why Blocking Install Scripts Doesn’t Stop a Malicious Package

npm Install-Time Trust | Xcitium Threat Labs Intelligence
Install-time gating Runtime execution
Decides once, when the dependency arrives Happens every time the application runs
Sees files, manifest and declared scripts Sees what the code actually does with real inputs
A package quiet at install passes cleanly That same package can still act later
Runs on the developer or build machine Runs wherever deployed, including production
Verdict before the code ever executes Verdict at the only moment code is observable
npm Version Dependency Lifecycle Scripts Git & Remote-URL Dependencies
≤ 11.15 Run automatically Resolved automatically
11.16.0+ (11.x) Still run, with warnings Still resolved, with warnings
12+ Blocked unless approved Blocked unless allowed

Opting back in is per project, not global, so the approval lives in the repository rather than in one developer’s environment.

Usually one unpaid volunteer, with no organisation or security budget behind them.
No obligation downstream. Licences disclaim warranty; not even a handover requires notice.
Accounts are the weak point. Whoever holds the account can publish, whoever wrote the code.
Names are the only check most consumers make, which is exactly what counterfeits exploit.
Reach is inherited. Most dependencies arrive transitively, never evaluated and often unknown.
Removal is not recall. Copies in lockfiles, caches, mirrors and images stay put.

The Repository Was Built To Survive A Review

The operators assumed that there would be a review of the source code. They created a GitHub organization called INDEXED-BTREE with the commit history from December 2025 to July 2026. The commits were tagged semantically with commit messages in pull request format. There was a maintainer character by the name of charlessadler25 who had an AI generated avatar.

This is where it gets interesting. The extended/ folder containing the sharedLoad.min.js file was present in the npm registry tarballs but nowhere in the public repository.

If anyone followed best practices and verified the source code, it was free from any malware. This was not the code that their build used.

Nine More Packages Sat Behind The Same Operation

The campaign extended well past a single library. Nine companion packages were removed alongside it: ordered-kv-indexbtree-leaderboardpriority-slot-queuebtree-range-storebtree-corebtree-time-indexbtree-lru-cacheneighbor-key-map and sliding-score-window.

Those nine account for more than 5.3 million downloads between them, with btree-core alone reported at 1,951,274. Across all ten packages the operators also controlled a wallet holding 109 ETH, worth roughly 230,933 euros.

Install-Time Gating Cannot See Runtime Behaviour

npm v12 works exactly as designed. The control it provides is real, and install hooks are a genuinely worse problem than they were three months ago. What the control cannot do is follow code into production.

Every install-time check shares one structural limit. It renders a verdict at the moment a dependency arrives, on evidence available at that moment. indexed-btree supplies clean evidence on purpose and holds its behaviour back until a specific value reaches a specific method, which might happen days or weeks later, inside a process that is supposed to be running.

This is the same gap that let a poisoned Virtualizor update install with root privileges after a BGP hijack. There, TLS validated correctly and the delivery path was trusted, so the package was accepted. Here, the install completes cleanly and the package is trusted, so the code is allowed to run.

Both cases put the same question to a defender. Once something untrusted has been admitted, what governs what it is permitted to do when it finally executes? Detection answers that question afterwards, by describing what happened. Execution control answers it at the moment of execution, which is the only moment where a runtime-triggered loader is visible at all.

Conclusion: A Clean Install Did Not Mean Clean Execution

The indexed-btree campaign demonstrates the limit of treating dependency installation as the final trust decision. npm v12 meaningfully reduces risk by preventing unapproved lifecycle scripts from executing automatically, but this package never needed an install script. Its malicious behavior remained dormant until ordinary application code reached the attacker’s runtime trigger.

That distinction moves the security boundary. A dependency can install without spawning a process, contacting infrastructure, or producing suspicious installation telemetry, then behave differently days later inside a production application. In this case, normal library execution could launch a detached Node.js process, collect host information, retrieve instructions through public infrastructure, and load additional payloads.

The repository deception makes that problem even harder. Reviewing a clean public source tree is not sufficient when the artifact delivered through the package registry contains files that are absent from the repository. Trust has to extend from source provenance to the exact artifact deployed and, ultimately, to what that code is permitted to do when it executes.

Why This Threat Matters

  • Install-time security covers only one execution stage. Malicious logic can remain dormant until the application actually uses the dependency.
  • Normal library functions can become triggers. Attackers do not need obvious lifecycle hooks when malicious behavior can hide inside frequently used application paths.
  • Repository review may not match the deployed artifact. The code developers inspect and the package they install can differ.
  • Production becomes the execution environment. A dependency quiet on a developer machine can activate later inside a server or application with greater access.
  • Public infrastructure complicates disruption. Blockchain-based command delivery reduces reliance on attacker-controlled domains and conventional hosting.

Where Defensive Control Must Operate

Xcitium Advanced EDR, powered by Xcitium’s patented Zero-Dwell platform, provides the runtime visibility needed when a trusted application unexpectedly launches child processes, creates network activity, or loads unfamiliar code.

Execution Governance adds the critical enforcement layer by deciding what previously untrusted code is permitted to do at execution time, rather than treating a clean installation as permanent authorization.

Trust Must Continue Into Runtime

Organizations should keep npm v12’s install-time restrictions enabled, but should not treat package approval as a permanent security verdict. Verify registry artifacts against reviewed source, minimize dependency reach, monitor unexpected child-process and network behavior, and enforce runtime boundaries wherever third-party code executes. Supply-chain trust should survive every stage from source to package to production execution.

Like what you see? Share with a friend.

Move Away From Detection With Patented Threat Prevention Built For Today's Challenges.

No one can stop zero-day malware from entering your network, but Xcitium can prevent if from causing any damage. Zero infection. Zero damage.

Book a Demo