indexed-btree npm Runtime Supply-Chain Malware

Threat Overview

Checkmarx research highlighted an npm supply-chain campaign centered on the malicious indexed-btree package, which impersonates the legitimate sorted-btree library. Unlike common npm malware, the package avoids install-time lifecycle scripts and hides execution inside normal runtime behavior. Reporting describes a trigger in BTree.prototype.set() and a secondary loader named sharedLoad.min.js. This design can bypass controls focused on preinstall, install, and postinstall scripts.

References

Impacted Systems

Node.js/npm developer workstations, CI/CD workers, build systems, and applications that installed affected malicious packages. indexed-btree versions 2.1.1, 2.1.2, and 2.1.3 were reported as malicious. npm replaced the package with a security placeholder. The legitimate sorted-btree package is not the same package.

Why this matters

Runtime-triggered supply-chain malware can expose developer credentials, source-code access, cloud credentials, and CI/CD secrets while evading defenses that focus only on package installation scripts.

Exploitation Status

Confirmed malicious package distribution and runtime execution behavior. Download statistics alone do not prove execution or compromise.

What this hunt looks for

Package artifacts, Node child-process behavior, runtime network connections, new destinations, credential-path access, script creation, and subsequent identity anomalies.

Required logs

Defender XDR DeviceFileEvents, DeviceProcessEvents and DeviceNetworkEvents, plus Microsoft Entra SigninLogs where available.

First Pass – indexed-btree Artifacts

DeviceFileEvents
| where Timestamp >= ago(30d)
| where FileName has_any ("indexed-btree","sharedLoad.min.js")
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,SHA256,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Package Manager References

DeviceProcessEvents
| where Timestamp >= ago(30d)
| where FileName in~ ("npm","npm.exe","npx","npx.exe","yarn","yarn.exe","pnpm","pnpm.exe","node","node.exe")
| where ProcessCommandLine has "indexed-btree"
| project Timestamp,DeviceName,AccountName,FileName,ProcessCommandLine,InitiatingProcessFileName
| order by Timestamp desc

Node Spawning Shells or Child Node

DeviceProcessEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node","node.exe")
| where FileName in~ ("node","node.exe","cmd.exe","powershell.exe","pwsh.exe","sh","bash","python","python3")
| project Timestamp,DeviceName,AccountName,InitiatingProcessCommandLine,FileName,ProcessCommandLine,SHA256
| order by Timestamp desc

Runtime Node Network Activity

DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node","node.exe")
| project Timestamp,DeviceName,InitiatingProcessAccountName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

New Node Network Destinations

let Known=DeviceNetworkEvents
| where Timestamp between (ago(30d)..ago(2d))
| where InitiatingProcessFileName in~ ("node","node.exe")
| summarize by DeviceId,RemoteUrl,RemoteIP;
DeviceNetworkEvents
| where Timestamp >= ago(2d)
| where InitiatingProcessFileName in~ ("node","node.exe")
| join kind=leftanti Known on DeviceId,RemoteUrl,RemoteIP
| project Timestamp,DeviceName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

Node Access to Credential Paths

DeviceFileEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node","node.exe")
| where FolderPath has_any (".ssh",".aws",".azure",".npm","AppData\\Roaming","Library/Application Support")
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,InitiatingProcessCommandLine
| order by Timestamp desc

Node-Created Scripts

DeviceFileEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node","node.exe")
| where ActionType =~ "FileCreated"
| where FileName endswith ".js" or FileName endswith ".ps1" or FileName endswith ".sh" or FileName endswith ".py"
| project Timestamp,DeviceName,FileName,FolderPath,SHA256,InitiatingProcessCommandLine
| order by Timestamp desc

Identity Follow-Up – New Sign-In IPs

let Known=SigninLogs
| where TimeGenerated between (ago(30d)..ago(2d))
| summarize by UserPrincipalName,IPAddress;
SigninLogs
| where TimeGenerated >= ago(2d)
| join kind=leftanti Known on UserPrincipalName,IPAddress
| project TimeGenerated,UserPrincipalName,IPAddress,AppDisplayName,ClientAppUsed,ResultType,LocationDetails
| order by TimeGenerated desc

Detection Notes

The package name and sharedLoad.min.js are higher-signal pivots. Node network and child-process activity is common in developer environments and requires context. Endpoint telemetry is required to observe runtime behavior.

Leave a comment