The National Vulnerability Database has published a cluster of five CRITICAL, network-vector CVEs against Node.js's most widely used sandboxing library, vm2, within the last three days. The most severe — CVE-2026-92937 (CVSS 10.0) — is a sandbox escape in vm2 3.11.6 caused by an incomplete fix for the previously disclosed GHSA-m283-3h24-438v, and it results in unauthenticated code execution in the host Node.js process. Two additional CVEs (CVE-2026-92956, also CVSS 10.0, and CVE-2026-92938, CVSS 9.9) round out a disclosure wave that should alarm any organization executing untrusted or semi-trusted JavaScript — SaaS platforms with user-scripting features, plugin systems, CI/CD runners, low-code workflow engines, code-eval APIs, and multi-tenant serverless functions.
If your application calls new VM() or new NodeVM() on attacker-influenced input, assume host compromise is achievable until you patch and verify. This post breaks down the vulnerabilities, gives you concrete hunting content for your SOC, and lays out a remediation path — including the hard truth that vm2 patching alone may no longer be a defensible long-term strategy.
Technical Analysis
The CVE Cluster
| CVE | CVSS | Affected Versions | Summary |
|---|---|---|---|
| CVE-2026-92937 | 10.0 (Critical) | vm2 3.11.6 | Sandbox escape via incomplete fix for GHSA-m283-3h24-438v; unauthenticated RCE in host process |
| CVE-2026-92956 | 10.0 (Critical) | vm2 3.10.1 – 3.11.6 | Sandbox escape reachable from a default new VM() configuration — no exotic options required |
| CVE-2026-92938 | 9.9 (Critical) | vm2 3.11.3 – 3.11.6 | Exposes Node.js host internals to sandboxed code, enabling escape primitives |
| CVE-2026-92948 | Critical (network-vector) | vm2 (per NVD disclosure) | Part of the same disclosure wave; details pending full NVD enrichment |
| CVE-2026-92944 | Critical (network-vector) | vm2 (per NVD disclosure) | Part of the same disclosure wave; details pending full NVD enrichment |
CVE-2026-92937: The Incomplete Promise-Bridge Fix
This is the most instructive bug of the five for defenders, because it demonstrates a pattern we've seen repeatedly in sandbox security: a patch that validates the direct call target but not the registration path.
The original GHSA-m283-3h24-438v fix introduced a bridge gate at lib/bridge.js:1624. When a host Promise is rejected, that gate decides whether to rebuild and sanitize the rejection value before handing it across the sandbox boundary. The problem: the gate only identity-checks the direct call target when deciding whether sanitization is required. If an attacker registers the rejection handler through an intermediary — e.g., chaining through another handler, .then() composition, or any indirect registration path — the identity check fails to match, the gate is bypassed, and an unsanitized host object crosses into the sandbox context.
From there, the exploitation chain is the classic vm2 escape pattern:
- Attacker-supplied code running inside the VM triggers a rejected host Promise through the indirect handler path.
- An unsanitized host-side object (with reachable prototype chain into the host realm) leaks into the sandbox.
- The attacker walks the prototype chain to reach the host
Functionconstructor orprocessobject. - Arbitrary code executes in the host Node.js process with the full privileges of that process — filesystem, network, environment variables (including cloud credentials and secrets), and child process spawning.
Because the vulnerable code path lives in a library consumed by network-facing applications, the CVE is scored as network-exploitable: the attacker never touches your server directly — your application runs their payload for them.
CVE-2026-92956: Escape from a Default Configuration
CVE-2026-92956 is arguably more dangerous operationally. It affects vm2 3.10.1 through 3.11.6 and is reachable from a default new VM() sandbox — meaning developers who followed the library's most basic, documented usage pattern are exposed. There is no misconfiguration prerequisite, no optional flag, no unusual API surface. If you instantiate a VM and run untrusted input in it, you are in the blast radius.
CVE-2026-92938: Host Internals Exposure
CVE-2026-92938 (CVSS 9.9) affects vm2 3.11.3 through 3.11.6 and exposes Node.js host internals into the sandbox context. Even where it does not directly constitute a full escape on its own, it provides the primitive — access to host-realm objects — that turns other weaknesses into reliable code execution. Treat it as escape-equivalent for risk purposes.
Exploitation Status
- These are freshly published NVD entries (within the last 3 days). At time of writing, inclusion in the CISA Known Exploited Vulnerabilities catalog has not yet been confirmed, and no mass-exploitation campaign has been publicly attributed.
- However, vm2 has a long and public history of sandbox escapes with rapidly weaponized PoCs. The technical detail already present in the CVE-2026-92937 description (file, line number, bypass mechanism) is sufficient for a competent researcher to reconstruct an exploit quickly.
- Treat exploitation as imminent, not theoretical. Any internet-facing service that evaluates user-supplied JavaScript via vm2 should be considered an active target.
Who Is Actually Exposed
Run this thought experiment against your environment. You are exposed if any of the following are true:
- Your product offers user-defined scripts, transformations, webhooks-with-code, formula engines, or plugin runtimes backed by vm2.
- You run a code-execution-as-a-service feature (interview platforms, eval APIs, educational sandboxes, bots with JS scripting).
- A third-party dependency in your tree pulls in vm2 — check your lockfiles, not just
package.json. Transitive exposure is the silent killer here.
Detection & Response
The post-exploitation behavior of a vm2 escape is highly observable: a Node.js process suddenly doing things Node.js application processes normally don't do — spawning shells, writing executables to disk, enumerating the environment, and making unexpected egress connections. That is your detection surface.
Sigma Rules
---
title: Node.js Process Spawning Shell or System Utility
tid: 9f4c2a71-3b8e-4d1a-b6c7-2e5f8a0d9c3b
status: experimental
description: Detects Node.js processes spawning shells or system utilities, consistent with post-exploitation behavior following a vm2 sandbox escape (CVE-2026-92937, CVE-2026-92956) where attacker code gains host-process execution and invokes child_process.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-92937
- https://attack.mitre.org/techniques/T1059/007/
author: Security Arsenal
date: 2026/02/14
tags:
- attack.execution
- attack.t1059.007
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|endswith:
- '\node.exe'
- '\nodejs.exe'
selection_child:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\rundll32.exe'
- '\certutil.exe'
- '\bitsadmin.exe'
- '\mshta.exe'
- '\curl.exe'
- '\whoami.exe'
- '\net.exe'
condition: selection_parent and selection_child
falsepositives:
- Build tooling and test harnesses that legitimately shell out from Node (restrict by install path or service account where possible)
- Electron-based development tools
level: high
---
title: Node.js Process Spawning Shell on Linux
tid: 4d8b1e63-7c2f-4a59-9d3e-1b6a5f0c8e2d
status: experimental
description: Detects a Linux node process spawning shells or reconnaissance utilities, a strong indicator of successful vm2 sandbox escape exploitation (CVE-2026-92937, CVE-2026-92956, CVE-2026-92938) via child_process in the host process.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-92937
- https://attack.mitre.org/techniques/T1059/004/
author: Security Arsenal
date: 2026/02/14
tags:
- attack.execution
- attack.t1059.004
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/node'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/python'
- '/python3'
- '/perl'
- '/curl'
- '/wget'
- '/id'
- '/whoami'
- '/uname'
- '/nc'
- '/ncat'
- '/base64'
condition: selection_parent and selection_child
falsepositives:
- Legitimate npm lifecycle scripts and build pipelines (filter CI runner hosts and known build paths)
- Node-based monitoring agents
level: high
---
title: Node.js Process Unexpected Outbound Network Connection
tid: 7e2a9d45-1f6b-4c83-a5d2-8b4e6c0f3a91
status: experimental
description: Detects Node.js processes establishing outbound connections to rare or raw-IP destinations, consistent with data exfiltration or C2 following a vm2 sandbox escape that yields host-process code execution.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-92937
- https://attack.mitre.org/techniques/T1071/001/
author: Security Arsenal
date: 2026/02/14
tags:
- attack.exfiltration
- attack.command_and_control
- attack.t1071.001
logsource:
category: network_connection
product: windows
detection:
selection:
Image|endswith:
- '\node.exe'
filter_known_destinations:
DestinationHostname|contains:
- '.npmjs.org'
- '.npmjs.com'
- '.githubusercontent.com'
- '.nodejs.org'
condition: selection and not filter_known_destinations
falsepositives:
- Application API traffic (baseline per-application egress and alert on deviation rather than using this as a standalone high-fidelity rule)
level: medium
KQL Hunt (Microsoft Sentinel / Defender)
This query hunts for the core post-escape signal — Node spawning shells or LOLBins — across both Defender endpoint telemetry and Syslog-ingested Linux hosts, and pairs it with an egress-deviation hunt for Node processes talking to rare destinations:
// Hunt 1: Node.js spawning shells/utilities (Windows + Linux via Defender)
let suspiciousChildren = dynamic(["cmd.exe","powershell.exe","pwsh.exe","rundll32.exe","certutil.exe","mshta.exe","curl.exe","whoami.exe","net.exe","sh","bash","dash","zsh","python3","perl","curl","wget","nc","ncat","base64","id","uname"]);
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe","node","nodejs")
| where FileName in~ (suspiciousChildren)
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, AccountName, InitiatingProcessId, ProcessId
| order by TimeGenerated desc;
// Hunt 2: Node.js egress to rare destinations (potential exfil/C2 post-escape)
let nodeConns = DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe","node","nodejs")
| where RemoteIPType == "Public";
let baseline = nodeConns
| summarize Count=count() by RemoteUrl, RemoteIP;
nodeConns
| summarize ConnectionCount=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Devices=dcount(DeviceName) by RemoteUrl, RemoteIP, RemotePort, InitiatingProcessCommandLine
| where ConnectionCount < 20
| order by ConnectionCount asc;
Velociraptor VQL Hunt
For DFIR teams validating whether a suspected vm2 escape has already occurred, this artifact identifies live Node processes with suspicious child processes and enumerates their network connections for triage:
-- Hunt for Node.js processes with suspicious children and anomalous connections
-- Triage artifact for suspected vm2 sandbox escape (CVE-2026-92937 / CVE-2026-92956)
LET node_procs = SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)node(\.exe)?$'
LET suspicious_spawn = SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)(cmd|powershell|pwsh|sh|bash|dash|curl|wget|nc|ncat|certutil|mshta|python|perl)(\.exe)?$'
AND Ppid IN (SELECT Pid FROM node_procs)
SELECT * FROM suspicious_spawn
UNION ALL
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime FROM node_procs
WHERE Pid IN (SELECT Ppid FROM suspicious_spawn)
-- Companion: enumerate network connections held by node processes
SELECT Pid, Name, Status, Family, Type, LocalIP, LocalPort, RemoteIP, RemotePort
FROM netstat()
WHERE Name =~ '(?i)node' AND Status = 'ESTABLISHED'
Verification & Remediation Script
Use this to inventory vm2 exposure across your Node.js deployments and verify patched state. Run it against application servers, container build contexts, and CI workspaces:
#!/bin/bash
# vm2 Exposure Inventory & Version Verification
# CVE-2026-92937 / CVE-2026-92956 / CVE-2026-92938 / CVE-2026-92948 / CVE-2026-92944
VULN_MAX="3.11.6"
echo "=== [1/4] Scanning for vm2 in dependency trees ==="
find /srv /opt /home /var/www -maxdepth 6 -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" 2>/dev/null | while read -r lock; do
if grep -q '"vm2"' "$lock" 2>/dev/null; then
echo "[!] vm2 found in: $lock"
grep -A2 '"node_modules/vm2"' "$lock" 2>/dev/null | grep '"version"' | head -1
fi
done
echo "=== [2/4] Installed vm2 versions on this host ==="
find / -maxdepth 8 -type d -name "vm2" -path "*node_modules*" 2>/dev/null | while read -r dir; do
ver=$(grep -m1 '"version"' "$dir/package.json" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
echo "vm2 $ver at $dir"
if [ -n "$ver" ]; then
if [ "$(printf '%s\n%s' "$VULN_MAX" "$ver" | sort -V | head -1)" != "$VULN_MAX" ] || [ "$ver" = "$VULN_MAX" ]; then
echo " [VULNERABLE] <= $VULN_MAX — update immediately per vendor advisory"
fi
fi
done
echo "=== [3/4] Checking for the incomplete bridge gate (CVE-2026-92937) ==="
find / -maxdepth 8 -path "*node_modules/vm2/lib/bridge.js" 2>/dev/null | while read -r f; do
echo "Inspecting $f line 1624 region:"
sed -n '1615,1635p' "$f" 2>/dev/null
done
echo "=== [4/4] Runtime audit — running node processes with vm2 loaded ==="
for pid in $(pgrep -x node); do
if ls -l /proc/$pid/cwd 2>/dev/null | grep -q .; then
cwd=$(readlink /proc/$pid/cwd)
if [ -d "$cwd/node_modules/vm2" ]; then
echo "[EXPOSED RUNNING PROCESS] PID $pid (cwd: $cwd) — restart after patching"
fi
fi
done
echo "=== Audit complete. Cross-reference findings with NVD advisories. ==="
Remediation
Immediate Actions (Next 24–72 Hours)
- Inventory every vm2 instance — direct and transitive. Run
npm ls vm2,yarn why vm2, or scan lockfiles across all repos and deployed artifacts. Transitive dependencies are where this will hide. - Upgrade vm2 beyond 3.11.6 the moment the maintainer releases patched builds covering all five CVEs. Pin explicitly — do not rely on semver ranges:
"vm2": "<exact-patched-version>"inpackage.json, and regenerate lockfiles. - If you cannot patch immediately, stop executing untrusted input. Disable user-scripting features, gate eval-style endpoints behind maintenance mode, or restrict them to fully trusted internal users. A CVSS 10 network-vector escape in a default configuration does not have a safe partial exposure state.
- Verify running processes are restarted post-patch. Node caches modules in memory; a deployed patch does nothing for a process started three weeks ago.
Compensating Controls (Defense-in-Depth, Even After Patching)
vm2's architectural model — emulating isolation inside the same V8 isolate and process — has produced escape after escape. Even fully patched, assume a future escape exists and build containment around that assumption:
- Run untrusted code in a separate, hardened process or container with no secrets in its environment, a read-only filesystem, a dedicated unprivileged UID, and strict seccomp/AppArmor profiles.
- Egress deny-by-default for anything executing user code. A successful escape with no outbound network path loses its exfiltration and C2 channel. This is the single highest-value compensating control.
- Drop ambient credentials. Ensure the Node process running sandboxes has no cloud instance metadata access (block 169.254.169.254), no mounted service-account tokens, and minimal IAM scope.
- Resource limits (CPU, memory, execution timeout) via worker thread or container cgroup constraints to limit post-exploitation dwell capability.
Strategic Recommendation: Migrate Off vm2
As practitioners, we have to say this plainly: vm2's track record — repeated critical escapes, including this cluster's incomplete fix of a prior fix — makes it a liability for any security-sensitive workload. Evaluate migration to:
isolated-vm— real V8 isolate boundaries with separate heaps, a fundamentally stronger isolation primitive.- Worker threads with hardened messaging for semi-trusted workloads.
- Container or microVM isolation (Firecracker-style, gVisor, Kata) for genuinely hostile multi-tenant code execution. Treat each tenant's code as a separate security boundary at the OS/virtualization layer, not the JavaScript layer.
Monitoring & Verification
- Deploy the Sigma rules and KQL hunts above; baseline Node child-process and egress behavior per application before the rules go to high severity, or you'll be tuning noise during a live incident.
- Add
npm audit/osv-scannerwith a fail-the-build policy on CRITICAL advisories for sandboxing libraries to CI. - Watch the NVD entries (CVE-2026-92937) and the vm2 GitHub security advisories for the remaining CVE details (CVE-2026-92948, CVE-2026-92944) as enrichment completes — patched version numbers for the full cluster should be confirmed against the official advisory before declaring remediation complete.
- If you find evidence of exploitation (Node spawning shells, unexpected egress), treat it as full host compromise: isolate the host, capture memory and disk, rotate all credentials and secrets accessible to that process, and engage IR.
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.