SecurityWeek reports that a malicious npm package named indexed-btree has accumulated millions of downloads by masquerading as the legitimate sorted-btree library. The package hides its payload behind a tampered prototype method — meaning the malicious code does not detonate on install or on import, but only when a specific method is invoked at runtime. That design choice matters enormously for defenders: this is a sleeper dependency engineered to survive static code review, casual sandboxing, and install-time scanners, and to blend into legitimate B-tree usage inside production Node.js workloads.
This is the latest in a long pattern of npm ecosystem abuse — typosquatting and name-confusion attacks against high-value open-source libraries — and it reinforces a hard truth: your production attack surface includes every transitive dependency pulled by every developer machine and CI runner in your organization. If a developer fat-fingered an install command, copied a code snippet from a poisoned Stack Overflow answer, or an AI coding assistant hallucinated this package name into a package.json, you may be running attacker-controlled code with the privileges of your Node.js process right now. Treat this as an active incident-response task, not a hygiene item.
Technical Analysis
What happened
- Malicious package:
indexed-btreeon the npm registry - Impersonated package:
sorted-btree, a legitimate and widely used B-tree implementation for JavaScript/TypeScript - Delivery mechanism: Name-confusion/typosquatting. The malicious name is close enough to the legitimate one to be installed via typo, misremembered package name, copy-paste from poisoned documentation, or dependency hallucination by AI coding assistants — a vector we are seeing with increasing frequency in 2025–2026.
- Payload concealment: The malicious trigger is embedded inside a prototype method of the exported class rather than in a
postinstallscript or top-level module code. This is a deliberate evasion technique:- Many dependency scanners flag install scripts (
preinstall/postinstall) and obvious top-level execution. A dormant method bypasses those heuristics. - The code only executes when the application calls the trojanized method — so the package can sit in
node_modulesinert during CI builds and security scanning, then activate in production. - Because the package otherwise behaves like a functional B-tree, runtime behavior appears normal until the trigger fires.
- Many dependency scanners flag install scripts (
Why prototype-method triggers are dangerous
In JavaScript, mutating or hiding logic on a class prototype lets an attacker interleave malicious behavior with legitimate API calls. From a defender's perspective, the observable behaviors once the trigger fires typically include:
- Process execution — the Node.js process spawning child processes (
cmd.exe,powershell.exe,/bin/sh,/bin/bash) viachild_process.exec/spawnto run second-stage payloads. - Network egress — the Node.js process making outbound HTTPS connections to non-registry, non-CDN infrastructure for data exfiltration or payload retrieval (often exfiltrating environment variables,
.npmrctokens,.envfiles, cloud credentials, or SSH keys). - File access — reads of sensitive paths (
~/.ssh,~/.aws/credentials,.env, CI runner token stores) by a process whose legitimate job is sorting data structures.
A B-tree library has zero legitimate reason to spawn a shell, read your SSH keys, or open arbitrary outbound connections. That asymmetry is your strongest detection anchor.
Exploitation status
- The package accumulated millions of downloads, meaning exposure is widespread and largely unintentional — the victims are downstream consumers who installed it by mistake.
- Because the trigger is runtime-gated, confirmed active exploitation in the wild is difficult to quantify from telemetry alone. Assume compromise wherever the package is present, and hunt for post-trigger behavior (child processes, egress) rather than just package presence.
- No CVE has been assigned for this package (malicious packages are typically removed from the registry rather than patched). Do not wait for a CVE to act — registry takedown does not remove copies already vendored, cached in artifact registries, or baked into container images.
Who is affected
- Any Node.js project with
indexed-btreeindependencies,devDependencies, or as a transitive dependency. - CI/CD runners and build agents where the package was installed (credential theft from build agents is the highest-impact scenario — these hold npm tokens, cloud deploy keys, and signing certificates).
- Published artifacts: container images, serverless bundles, and internal npm packages that bundled the library.
Detection & Response
Step 1 — Find the package everywhere
Before hunting for runtime behavior, establish exposure. Search every lockfile, node_modules tree, private registry cache, and container image in your estate. The presence of indexed-btree anywhere is a finding requiring investigation.
Sigma Rules
The following rules target the highest-signal post-trigger behavior: Node.js spawning shells and making suspicious outbound connections. They are intentionally narrow to avoid drowning your queue in legitimate Node.js build tooling noise — tune the exclusions to your build fleet, not to your production workloads.
---
title: Node.js Process Spawning Shell or Script Interpreter
tid: 3f8a2b1c-7d4e-4f5a-9b6c-2e1d0a9f8b7c
status: experimental
description: Detects node.exe spawning cmd.exe, powershell.exe, or other script interpreters, consistent with a malicious npm package trigger executing via child_process (e.g. indexed-btree typosquat payload).
references:
- https://www.securityweek.com/malicious-b-tree-npm-package-accumulates-millions-of-downloads/
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.supply_chain_compromise
- attack.t1195.002
- attack.t1059
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|endswith: '\node.exe'
selection_child:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\mshta.exe'
- '\curl.exe'
- '\certutil.exe'
filter_build_tools:
CommandLine|contains:
- 'node-gyp'
- 'prebuild-install'
condition: selection_parent and selection_child and not filter_build_tools
falsepositives:
- Native module compilation during npm install (node-gyp)
- Legitimate build orchestration scripts — restrict alerting to production servers and CI runners with pinned baselines
level: high
---
title: Node.js Process Making Outbound Connection to Non-Registry Destination
tid: 8c4d1e2f-3a5b-4c6d-8e7f-1a2b3c4d5e6f
status: experimental
description: Detects node.exe establishing outbound connections to destinations outside expected npm/CDN infrastructure, consistent with data exfiltration or second-stage payload retrieval from a trojanized dependency such as indexed-btree.
references:
- https://www.securityweek.com/malicious-b-tree-npm-package-accumulates-millions-of-downloads/
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1041/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.supply_chain_compromise
- attack.t1195.002
- attack.exfiltration
- attack.t1041
logsource:
category: network_connection
product: windows
detection:
selection:
Image|endswith: '\node.exe'
Initiated: 'true'
filter_rfc1918:
DestinationIp|startswith:
- '10.'
- '192.168.'
- '172.16.'
- '172.17.'
- '172.18.'
- '172.19.'
- '172.2'
- '172.30.'
- '172.31.'
- '127.'
filter_known_registries:
DestinationHostname|endswith:
- '.npmjs.org'
- '.npmjs.com'
- '.yarnpkg.com'
- '.github.com'
- '.githubusercontent.com'
condition: selection and not filter_rfc1918 and not filter_known_registries
falsepositives:
- Legitimate application API calls from Node.js services — apply this rule to build agents and CI runners first, where Node.js egress should be almost exclusively registry traffic
level: medium
---
title: Linux Node Process Executing Shell Utility
tid: 5e2f7a8b-9c1d-4e3f-a6b5-8d7c6e5f4a3b
status: experimental
description: Detects a Node.js process on Linux executing shell utilities or script interpreters, a hallmark of malicious npm package runtime triggers (e.g. the indexed-btree prototype-method payload invoking child_process).
references:
- https://www.securityweek.com/malicious-b-tree-npm-package-accumulates-millions-of-downloads/
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1059/004/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.supply_chain_compromise
- attack.t1195.002
- attack.t1059.004
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith: '/node'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/curl'
- '/wget'
- '/python'
- '/python3'
- '/base64'
- '/env'
filter_node_gyp:
CommandLine|contains:
- 'node-gyp'
- 'make '
condition: selection_parent and selection_child and not filter_node_gyp
falsepositives:
- Native addon builds (node-gyp invokes make/compilers)
- Process managers and deployment wrappers — baseline per host role
level: high
KQL Hunt (Microsoft Sentinel / Defender)
This query hunts for the runtime trigger across both workstation/server telemetry (Defender) and syslog-ingested Linux hosts. Prioritize hits on build agents and production servers.
// Hunt for Node.js spawning shells or sensitive-file access — indexed-btree style payload trigger
let ShellBinaries = dynamic(["cmd.exe","powershell.exe","pwsh.exe","mshta.exe","wscript.exe","cscript.exe","certutil.exe","/bin/sh","/bin/bash","sh","bash","curl","wget","base64","env","python3","python"]);
union isfuzzy=true
(DeviceProcessEvents
| where InitiatingProcessFileName =~ "node.exe" or InitiatingProcessFileName =~ "node"
| where FileName has_any (ShellBinaries)
| extend HostName = DeviceName, Parent = InitiatingProcessFileName, Child = FileName, Cmd = ProcessCommandLine, Account = InitiatingProcessAccountName
| project TimeGenerated, HostName, Parent, Child, Cmd, Account, Source = "Defender"),
(Syslog
| where Facility == "user" and SyslogMessage has "node" and SyslogMessage has_any ("/bin/sh","/bin/bash","child_process")
| extend HostName = HostName, Cmd = SyslogMessage
| project TimeGenerated, HostName, Cmd, Source = "Syslog")
| order by TimeGenerated desc
// Sentinel: Linux auditd/syslog hunt for Node.js reading credential files (post-trigger exfiltration behavior)
Syslog
| where TimeGenerated > ago(7d)
| where SyslogMessage has_all ("node", "open") or SyslogMessage has "node"
| where SyslogMessage has_any (".aws/credentials", ".ssh/id_", ".npmrc", ".env", ".netrc", "/etc/shadow", "kubeconfig")
| project TimeGenerated, HostName, ProcessName, SyslogMessage, SeverityLevel
| order by TimeGenerated desc
Velociraptor VQL
Use this artifact to sweep your fleet for the malicious package on disk — in node_modules trees, lockfiles, and build-agent caches. Presence of indexed-btree is itself a reportable finding.
-- Hunt for the malicious indexed-btree npm package across node_modules trees and lockfiles
LET hits_packages = SELECT FullPath, Size, Mtime
FROM glob(globs=['/**/node_modules/indexed-btree/package.json'], root='/')
LET hits_lockfiles = SELECT FullPath, Size, Mtime
FROM glob(globs=['/**/package-lock.json', '/**/yarn.lock', '/**/pnpm-lock.yaml'], root='/')
WHERE read_file(filename=FullPath, length=50000000) =~ 'indexed-btree'
SELECT FullPath AS ArtifactPath, Size, Mtime, 'package.json (installed copy)' AS FindingType FROM hits_packages
UNION ALL
SELECT FullPath AS ArtifactPath, Size, Mtime, 'lockfile reference' AS FindingType FROM hits_lockfiles
-- Windows variant: hunt for indexed-btree and node.exe spawning shells
LET pkg_hits = SELECT FullPath, Size, Mtime
FROM glob(globs=['C:/Users/*/node_modules/indexed-btree/**',
'C:/*/node_modules/indexed-btree/package.json',
'D:/*/node_modules/indexed-btree/package.json'])
SELECT FullPath, Size, Mtime FROM pkg_hits
-- Correlate with suspicious node child processes
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)cmd|powershell|pwsh|mshta|cscript|wscript'
AND Ppid IN (SELECT Pid FROM pslist() WHERE Name =~ '(?i)node')
Fleet Remediation / Exposure Scan Script
Run this on developer workstations, CI runners, and build hosts to enumerate exposure and capture evidence before removal.
#!/bin/bash
# indexed-btree exposure scanner - run via your RCM/EDR or Ansible across the fleet
# Finds installed copies, lockfile references, and evidence of execution
REPORT="/tmp/indexed_btree_scan_$(hostname)_$(date +%Y%m%d).txt"
{
echo "=== indexed-btree exposure scan: $(hostname) $(date -Is) ==="
echo "[1] Installed package copies (node_modules):"
find / -type d -name 'indexed-btree' -path '*node_modules*' 2>/dev/null
echo "[2] Lockfile references:"
grep -rls 'indexed-btree' \
--include='package-lock.json' \
--include='yarn.lock' \
--include='pnpm-lock.yaml' \
/home /root /opt /srv /var/lib 2>/dev/null
echo "[3] package.json direct references:"
grep -rls '"indexed-btree"' --include='package.json' \
/home /root /opt /srv 2>/dev/null
echo "[4] npm cache copies:"
find /root/.npm /home/*/.npm -iname '*indexed-btree*' 2>/dev/null
echo "[5] Running node processes with suspicious children:"
for pid in $(pgrep -x node 2>/dev/null); do
children=$(pgrep -P "$pid" 2>/dev/null)
for c in $children; do
cname=$(cat /proc/$c/comm 2>/dev/null)
case "$cname" in
sh|bash|dash|curl|wget|python*|base64)
echo " node[$pid] -> $cname[$c]: $(cat /proc/$c/cmdline 2>/dev/null | tr '\0' ' ')";;
esac
done
done
echo "[6] Recent node shell history artifacts (auditd, if present):"
ausearch -k node_exec 2>/dev/null | tail -20 || echo " (auditd key not configured)"
} | tee "$REPORT"
echo "Report written to $REPORT — forward to the IR team before remediation."
# Windows variant: scan for indexed-btree exposure and suspicious node child processes
$Report = "$env:TEMP\indexed_btree_scan_$env:COMPUTERNAME.txt"
$results = @()
# 1. Find installed copies and lockfile references
$searchRoots = @("C:\Users", "C:\src", "C:\projects", "D:\") | Where-Object { Test-Path $_ }
foreach ($root in $searchRoots) {
Get-ChildItem -Path $root -Recurse -Directory -Filter "indexed-btree" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'node_modules' } |
ForEach-Object { $results += "INSTALLED: $($_.FullName)" }
Get-ChildItem -Path $root -Recurse -Include package-lock.json, yarn.lock, pnpm-lock.yaml -ErrorAction SilentlyContinue |
Where-Object { (Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue) -match 'indexed-btree' } |
ForEach-Object { $results += "LOCKFILE: $($_.FullName)" }
}
# 2. Check npm cache
Get-ChildItem "$env:LOCALAPPDATA\npm-cache" -Recurse -Filter "*indexed-btree*" -ErrorAction SilentlyContinue |
ForEach-Object { $results += "CACHE: $($_.FullName)" }
# 3. Detect node.exe with suspicious child processes right now
$nodeProcs = Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue
foreach ($np in $nodeProcs) {
Get-CimInstance Win32_Process -Filter "ParentProcessId=$($np.ProcessId)" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match 'cmd|powershell|pwsh|mshta|cscript|wscript|certutil' } |
ForEach-Object { $results += "LIVE-EXEC: node[$($np.ProcessId)] -> $($_.Name) [$($_.ProcessId)]: $($_.CommandLine)" }
}
$results | Out-File $Report
if ($results.Count -gt 0) {
Write-Warning "indexed-btree exposure or suspicious node execution found. See $Report — escalate to IR before deleting anything."
} else {
Write-Output "No indexed-btree indicators found on $env:COMPUTERNAME."
}
Remediation
- Remove the package — but preserve evidence first. If
indexed-btreeis found anywhere, capture the installed directory, the lockfile, the installing user's identity, and the install timestamp before deletion. A malicious install on a build agent is an incident, not a cleanup task. - Pin and reinstall from a clean lockfile. Delete
node_modules, remove the malicious entry from the lockfile, regenerate it against the legitimatesorted-btreepackage, and reinstall. Verify integrity hashes (npm cienforces lockfile integrity — prefer it overnpm installin CI). - Rotate credentials on any host where the package was installed. This is non-negotiable. Runtime-triggered npm malware classically harvests environment variables and credential files. Rotate npm tokens, CI/CD secrets, cloud provider keys, SSH keys, and any
.envsecrets present on affected hosts. Assume exfiltration occurred if the host ever executed the application with the dependency loaded. - Purge caches and artifacts. Clear npm caches on affected machines (
npm cache clean --force), purge the package from internal artifact registries (Artifactory/Nexus/Verdaccio) and remote caches, and rebuild any container images or serverless bundles that included it. Scan historical image layers in your registry — takedown from npm does nothing for images already built. - Block the package at the perimeter of your supply chain. Add
indexed-btreeto your artifact repository's block list and your dependency firewall (e.g., artifact proxy quarantine rules). Also block it at the DNS/egress layer for registry-mirror traffic if applicable. - Institutionalize typosquat defenses:
- Enforce
npm ciwith committed, reviewed lockfiles in CI — no ad-hocnpm install <name>in pipelines. - Deploy dependency-firewall tooling or registry proxies that score new/unusual packages before they're consumable by developers.
- Enable npm's
--ignore-scriptsfor CI installs where native builds aren't required, and audit packages that require install scripts. - Add a pull-request gate that diffs
package.jsonand lockfiles and flags any new dependency with low download counts, recent publish dates, or names within edit distance of popular packages. - Address AI-assistant package hallucination in your secure development guidance — code suggestions referencing plausible-but-wrong package names are a growing initial-access vector, and
indexed-btree-style squatters profit from exactly that behavior.
- Enforce
- Hunt retroactively. Run the VQL and scripts above across your full estate, including ephemeral build-agent images and developer laptops. Then run the Sigma/KQL detections over the last 30–90 days of telemetry to determine whether the trigger ever fired in your environment — package presence tells you about exposure; child-process and egress telemetry tells you about impact.
The Bottom Line
indexed-btree is a textbook evolution of the npm typosquat playbook: functional decoy code, a runtime-gated trigger hidden on a prototype method, and a distribution model that monetizes simple human error at scale. The defensive lesson is not "audit harder" — it's that dependency presence checks are only the first half of the job. Because the payload sleeps until invoked, your detection strategy must pair software-composition inventory (what's in the tree?) with runtime behavioral detection (is Node.js doing things a sorting library never should?). Close both gaps, rotate credentials wherever exposure is confirmed, and treat every affected build agent as a potential secrets-compromise incident.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.