May 4, 2026·7 min read·104 visits
vm2 prior to 3.11.0 fails to properly isolate JavaScript realms, allowing attackers to leverage context-confusion, reflective API traps, and V8's ArraySpeciesCreate algorithm to leak the host Function constructor and achieve remote code execution.
A critical vulnerability in the vm2 Node.js sandbox library allows attackers to bypass isolation mechanisms and execute arbitrary code on the host system. The flaw stems from insufficient sanitization of cross-realm object interactions and V8 internal algorithm behaviors.
The vm2 library is a Node.js module designed to execute untrusted code in a highly restricted sandbox environment. It implements an isolation membrane using JavaScript Proxies to intercept and sanitize cross-realm object interactions between the host environment and the sandbox. This architecture intends to prevent sandbox code from accessing core Node.js APIs or modifying the host context.
Prior to version 3.11.0, this proxy-based membrane contains critical implementation flaws related to edge cases in JavaScript property accessors and native V8 internal algorithms. The vulnerability, tracked as CVE-2026-24118, allows an attacker executing untrusted code within the sandbox to leak direct references to host-realm objects. This constitutes a complete sandbox escape and fundamentally breaks the security model of the library.
Successful exploitation results in arbitrary code execution with the privileges of the Node.js process running the vm2 instance. By leaking the host's Function constructor, an attacker completely escapes all sandbox constraints. They can subsequently require native modules such as child_process to execute arbitrary system commands, compromising the entire host infrastructure.
The vulnerability relies on a combination of three distinct primitives to break the proxy membrane. The first is a context-confusion primitive. Attackers utilize a host-context function's apply method to invoke __lookupGetter__ on host-realm objects. The vm2 bridge misidentifies the call origin, returning an un-proxied getter for sensitive properties like __proto__. This initial leak provides the foundation for traversing the host's prototype chain.
The second primitive involves bypassing vm2's defensive traps against reflective metadata access. While the library blocks direct access to the .constructor property, attackers use reflective methods such as Object.getOwnPropertyDescriptor and Object.entries. These methods extract the constructor values directly from property descriptors, effectively bypassing standard property-access interception and exposing the host objects to the sandbox context.
The final primitive exploits the behavior of the ArraySpeciesCreate algorithm internal to the V8 engine. Native mutating methods like Array.prototype.map or Array.prototype.slice read this.constructor[Symbol.species] directly on the underlying raw object, circumventing proxy handlers. V8 executes the mapping and stores raw host values directly into the target array via internal C++ methods, bypassing the vm2 bridge sanitization entirely.
The original vulnerability stems from the fundamental difficulty of intercepting all property accesses in a complex language specification like ECMAScript. The vm2 proxy handler failed to account for native C++ V8 methods that extract values without triggering the standard get handlers. The fix required a multi-layered defense-in-depth approach spanning multiple commits.
Commit f9b700b1c7d9ef2df416666cb24e0b659140cc74 addresses the ArraySpeciesCreate bypass by implementing a neutralize-and-restore pattern. Before every sandbox-to-host function invocation (such as apply or construct traps), the bridge iterates through the context and top-level arguments. For every host array found, it installs constructor = undefined as an own data property. This forces the internal V8 algorithm to fall back to the safe default Array constructor. The original descriptor is subsequently restored in a finally block to preserve legitimate application functionality.
// Conceptual representation of the neutralize-and-restore pattern
try {
if (Array.isArray(hostObj)) {
// Neutralize constructor to prevent Symbol.species abuse
Object.defineProperty(hostObj, 'constructor', {
value: undefined, configurable: true
});
}
// Execute cross-realm call
return Reflect.apply(target, thisArgument, argumentsList);
} finally {
// Restore original descriptor
if (Array.isArray(hostObj)) {
delete hostObj.constructor;
}
}Commit 2b5f3e3a060d9088f5e1cdd585d683d491f990a3 implements descriptor sanitization to neutralize the reflective bypasses. The getOwnPropertyDescriptor trap was modified to enforce strict, recursive sanitization. Any descriptors returned to the sandbox now have their value, get, and set properties thoroughly wrapped by the proxy bridge, preventing the leakage of host-context primitives through metadata inspection.
Exploitation begins with the attacker harvesting a host object constructor. This is achieved by chaining Buffer.apply and __lookupGetter__. The operation tricks the vm2 context verification logic into identifying the operation as a legitimate host-side action. The attacker traverses the prototype chain until the host Function or Object constructor is isolated.
Next, the attacker initiates the host array acquisition and mutation phase. Using the leaked host constructor, a host-realm array is minted. The attacker then defines a malicious sandbox constructor specifically tailored to self-return via Symbol.species. This malicious constructor is assigned to the host array's .constructor property, preparing the data structure for the V8 internal bypass.
// Step 1: Harvest Host Object
const g = ({}).__lookupGetter__;
const a = Buffer.apply;
const p = a.apply(g, [Buffer, ['__proto__']]);
const op = p.call(p.call(p.call(p.call(Buffer.of()))));
const ho = op.constructor;
// Step 2 & 3: Array Minting and Mutation (Conceptual)
// The attacker triggers r.map() causing V8 to read the malicious species
// and write raw host data back to the array, bypassing the proxy.The trigger execution occurs when the attacker invokes a mutating function like map() on the prepared array. The V8 engine executes the internal mapping algorithm, which ignores the vm2 proxy handlers and directly writes raw host values into the attacker-controlled structure. The attacker then extracts the host Function constructor from this mutated array and spawns a child_process instance to execute system commands.
The security impact of CVE-2026-24118 is total system compromise. Successful exploitation transitions the attacker from a restricted JavaScript runtime boundary to the host operating system. The attacker achieves arbitrary command execution under the security context of the user running the Node.js application. This negates the primary purpose of the vm2 library entirely.
The vulnerability carries a CVSS v3.1 base score of 9.8 (CRITICAL). The attack vector is Network (AV:N), as an attacker can typically submit the malicious payload via any network interface that accepts user scripts. It requires no privileges (PR:N) and no user interaction (UI:N). The impact on confidentiality, integrity, and availability is universally High (C:H/I:H/A:H).
Organizations using vm2 for multi-tenant isolation, automated code grading, Serverless functions, or plugin evaluation face severe operational risks. Exploitation exposes all internal environment variables, database credentials, and file system contents accessible to the Node.js process. The host server can also be pivoted to attack adjacent systems within the internal network infrastructure.
The immediate remediation step is updating the vm2 dependency to version 3.11.0. This patched version incorporates the necessary proxy handler hardening, the neutralize-and-restore pattern for array operations, and comprehensive descriptor sanitization. No alternative workarounds or configuration changes exist to secure vulnerable versions of the library.
Despite the release of version 3.11.0, the vm2 project has been officially deprecated by its maintainers. The maintainers cited the inherent, unresolvable architectural difficulty of securing a proxy-based JavaScript membrane against the complex and evolving V8 engine. The continuous discovery of similar sandbox escapes demonstrates that pure JavaScript virtualization within a shared V8 isolate is fundamentally unsound for security boundaries.
Security teams and software developers must implement a migration strategy to replace vm2. Recommended alternatives prioritize hardware-level or memory-safe boundaries. Options include running untrusted code in separate processes with restrictive operating system privileges, utilizing containerization technologies like Docker or gVisor, or adopting WebAssembly (Wasm) runtimes designed specifically for secure execution.
The following sequence illustrates the exploitation chain from initial sandbox entry to the execution of host-level system commands. The flow demonstrates how the proxy membrane is bypassed via internal engine mechanisms.
The diagram highlights that the core failure is not a single missing check, but rather the interaction between the host environment, the proxy wrapper, and the lower-level C++ runtime execution model.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
vm2 patriksimek | < 3.11.0 | 3.11.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-693 |
| Attack Vector | Network |
| CVSS Base Score | 9.8 CRITICAL |
| EPSS Score | N/A |
| Impact | Remote Code Execution (Sandbox Escape) |
| Exploit Status | Weaponized PoC Available |
| KEV Status | Not Listed |
Protection Mechanism Failure / Sandbox Escape
CVE-2026-54720 is a stored Cross-Site Scripting (XSS) vulnerability inside the Silverstripe Framework's media shortcode processor. Due to a flawed performance optimization, HTML inputs containing two or fewer opening angle brackets bypassed security sandboxing. This flaw allows authenticated or lower-privileged users to inject administrative panel payloads that execute arbitrary client-side JavaScript when viewed by system administrators.
An incomplete array comparison vulnerability in cakephp/queue version 0.1.11 through 2.3.0 allows unauthenticated attackers to cause key collisions in unique job deduplication. This is caused by standard array value sorting that discards associative keys, normalizing different payload keys to identical arrays and leading to a denial of service (DoS) by dropping legitimate jobs.
An input buffering vulnerability exists in the aiosmtplib asynchronous SMTP client library before version 5.1.2. When upgrading a plaintext connection to TLS via STARTTLS, the library processes buffered plaintext responses after transport negotiation has completed. This behavior allows a network-positioned attacker to inject spoofed server responses prior to negotiation, leading to command/response desynchronization, arbitrary capability injection, and potential credential theft.
An open redirect vulnerability exists in WebOb before version 1.8.11 due to a parser differential between WebOb's validation logic and Python's standard urllib.parse.urljoin() function. Under Python 3.10+, the urljoin function strips leading and trailing space characters and C0 control characters, which allowed specially crafted inputs to bypass WebOb's prefix checks while still resolving as off-host redirects.
Prior to version 1.0.0, the n8n-nodes-sqlite3 integration exposed the db_path parameter as an unrestricted node parameter. By default, n8n node parameters allow the evaluation of dynamic data expressions, meaning untrusted external input could be mapped to the database path. This vulnerability allows an external attacker to control which SQLite database file the n8n backend process attempts to open, leading to directory traversal outside of the intended directory context.
A client-side open redirect vulnerability has been identified in the Kargo user interface. The flaw resides in the handling of OpenID Connect (OIDC) login and token renewal flows, where the application extracts an unvalidated destination path from the redirectTo query parameter. Attackers can exploit this to redirect authenticated users to arbitrary external domains.