Detect vm2 NodeVM Custom Resolver Path Boundary Bypass (CVE-2026-100721) in Elastic Security
Detects exploitation and presence of CVE-2026-100721, a CVSS 10.0 authorization bypass (CWE-863) in the npm package vm2 at versions <= 3.12.1. When a NodeVM is configured with a custom module resolver, crafted require paths can bypass the configured external path boundaries, allowing sandboxed code to resolve and load modules outside the intended allowlist and escape the sandbox. This detection surfaces vulnerable vm2 installs, suspicious require() resolution patterns consistent with boundary bypass, and child-process/filesystem activity spawned from Node.js hosts running vm2.
MITRE ATT&CK
- Tactic
- Execution Defense Evasion
Elastic Detection Query
process where event.type == "start" and
process.parent.name : ("node", "node.exe") and
(
process.name : ("sh", "bash", "cmd.exe", "powershell.exe", "/bin/sh") or
process.command_line : ("*constructor.constructor*", "*mainModule.require*", "*require('child_process')*", "*process.binding*")
) EQL rule matching child-process starts parented by node that match sandbox-escape primitives used to exploit the vm2 custom-resolver boundary bypass.
Data Sources
Required Tables
False Positives & Tuning
- Node build tooling invoking shells
- Process supervisors launching node and shells in the same tree
- Legitimate server-side rendering frameworks shelling out to converters
Other platforms for CVE-2026-100721
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Inventory vulnerable vm2 version
Expected signal: Process creation for node reading vm2/package.json; file read of node_modules/vm2/package.json
- Test 2Simulate NodeVM custom resolver boundary bypass (lab only)
Expected signal: node process spawns a child shell (sh) running 'id'; command line contains constructor.constructor and mainModule.require
- Test 3Node spawns shell via child_process
Expected signal: ProcessRollup/auditd event: parent node, child sh/bash running id
References (6)
- https://github.com/patriksimek/vm2/security/advisories/GHSA-5h3f-q97h-ccvc
- https://nvd.nist.gov/vuln/detail/CVE-2026-100721
- https://github.com/patriksimek/vm2/releases/tag/v3.12.2
- https://www.vulncheck.com/advisories/vm2-before-3.12.2-authorization-bypass-via-custom-resolver
- https://github.com/advisories/GHSA-5h3f-q97h-ccvc
- https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72
Response Playbook
Triage
- Confirm the vm2 version on the affected host by inspecting node_modules/vm2/package.json; any version <= 3.12.1 is vulnerable and must be upgraded to 3.12.2 or later.
- Determine whether the application uses NodeVM with a custom module resolver (customResolver / resolve option) and whether it executes untrusted or user-supplied code — if yes, treat as actively exploitable.
- Correlate the alerting node process with its command line and parent/child tree to confirm whether a shell or unexpected module load occurred around the alert time.
- Review application logs for require() calls resolving paths outside the configured external allowlist directories.
Containment
- Isolate the affected host or container from the network if a sandbox escape (shell spawn, unexpected child process) is confirmed.
- Disable or take offline the service that evaluates untrusted code via vm2 until the package is upgraded to >= 3.12.2.
- Rotate any credentials, tokens, or keys accessible to the Node.js process, since a vm2 escape grants full host-level code execution.
Evidence Collection
- Capture the full process tree (node parent and all children), their command lines, and loaded module paths for the incident window.
- Preserve node_modules/vm2/package.json, the application source invoking NodeVM, and any custom resolver configuration for version and exploitability confirmation.
- Collect application and container logs showing the require/resolve calls and any filesystem reads outside the intended boundary.
Escalation Criteria
- !Escalate to incident response if a shell, child_process, or outbound connection was spawned from the node process hosting vm2.
- !Escalate if the vulnerable service is internet-facing or accepts code/templates from untrusted users.
- !Escalate if evidence shows module resolution outside the configured external path boundary, indicating successful bypass exploitation.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
node_modules/vm2/package.json showing version <= 3.12.1 - >
Process creation records where node is the parent of a shell or child_process - >
Application logs of require()/resolve() calls referencing paths outside the configured external boundary
Tuning Guidance
Baseline which Node.js applications legitimately spawn child processes (build tools, SSR frameworks, CI runners) and exclude those parent/child pairs. Focus severity on hosts that both carry vm2 <= 3.12.1 and run a service that evaluates untrusted code via NodeVM with a custom resolver. Reduce noise from SCA scanners by scoping file-event rules to execution-correlated events rather than package.json reads alone.
Hunting Queries
Inventory all hosts carrying a vm2 package to triage which installations are <= 3.12.1 and reachable with untrusted input.
DeviceFileEvents | where FolderPath matches regex @"node_modules[\\/]+vm2[\\/]+package\.json$" | project DeviceName, FolderPath, Timestamp index=* sourcetype=*package* path="*node_modules/vm2/package.json*" | stats values(version) by host Atomic Red Team Tests
Check the installed vm2 version to confirm exposure to CVE-2026-100721 (vulnerable <= 3.12.1).
Command
node -e "console.log(require('vm2/package.json').version)" || cat node_modules/vm2/package.json | grep '"version"' Cleanup
echo 'no cleanup required - read-only check' Expected Telemetry
Process creation for node reading vm2/package.json; file read of node_modules/vm2/package.json
Expected Detection
File-event query surfaces the vm2 package.json path for version triage
Run a NodeVM sandbox with a custom resolver and attempt to resolve a module outside the configured external path boundary to reproduce the authorization bypass.
Command
node -e "const {NodeVM}=require('vm2');const vm=new NodeVM({require:{external:true,root:'./allowed'},customRequire:(p)=>require(p)});vm.run(\"const cp=this.constructor.constructor('return process')().mainModule.require('child_process');cp.execSync('id');\",'exploit.js')" Cleanup
echo 'no artifacts to clean' Expected Telemetry
node process spawns a child shell (sh) running 'id'; command line contains constructor.constructor and mainModule.require
Expected Detection
Process-creation rules match the node->sh child with sandbox-escape primitives
Baseline the detectable primitive: a node process spawning a shell that executes a command, mirroring post-escape behavior.
Command
node -e "require('child_process').execSync('id', {stdio:'inherit'})" Cleanup
echo 'no cleanup required' Expected Telemetry
ProcessRollup/auditd event: parent node, child sh/bash running id
Expected Detection
EQL/CQL/KQL rules match node-parented shell execution