Oct 2, 2026·6 min read·5 visits
A critical bypass in the vm2 sandbox proxy lets untrusted code use Function call/apply indirection to bypass Promise rejection sanitizers, leading to remote code execution.
CVE-2026-92937 is a critical sandbox escape vulnerability in the `vm2` Node.js library. Due to a logical failure in checking direct invocation targets inside the Proxy bridge, an attacker can register Promise callbacks using `Function.prototype.call` or `Function.prototype.apply` indirection. This bypasses the error sanitization wrappers, delivering raw host error objects directly to sandboxed callbacks and allowing the attacker to escape the sandbox and execute arbitrary shell commands on the host.
The component vm2 is a widely utilized Node.js library designed to run untrusted code in an isolated environment. It attempts to create a secure context boundary by intercepting operations between the host and sandboxed environments through a custom Proxy-based bridge mechanism.
The vulnerability tracked as CVE-2026-92937 represents a critical boundary-crossing flaw where sandboxed code bypasses safety wrapper logic designed to sanitize rejection errors. The vulnerability allows an attacker to leak a raw host-realm error object, bypassing the sandbox isolation guarantees.
Once an attacker obtains a direct reference to a host object from the leaked error, they can abuse standard Node.js API paths to achieve arbitrary remote code execution on the host machine. The vulnerability occurs due to a logical mismatch in how the proxy's apply trap evaluates target execution contexts.
The root cause of this vulnerability lies in the sanitization logic implemented in lib/bridge.js to address the prior advisory GHSA-m283-3h24-438v. The library used an identity verification gate inside the Proxy bridge apply trap to determine if a call was targeting Promise.prototype.then or Promise.prototype.catch on a host Promise.
When these methods were called directly, the proxy wrapped the registered callbacks in a sanitizer function, which stripped raw host properties from any rejection error. However, the check only inspected the direct target of the invocation represented by the object variable in the apply trap.
By invoking the target methods indirectly using Function.prototype.call or Function.prototype.apply, sandboxed code causes the proxy trap to receive the helper function (e.g., call) as the direct target instead of the underlying then or catch method. Because the target identity does not match the expected Promise prototype methods, the sanitization wrapper bypasses the callbacks entirely, allowing direct access to the raw error object on rejection.
Prior to the fix, the vulnerability gate inside lib/bridge.js evaluated only the direct target function. The logic was vulnerable to indirect call chains that obscured the identity of the underlying Promise method:
if (!isHost && hostPromiseSanitizeReject !== null) {
if (isHostPromiseThen(object)) {
args = wrapHostPromiseThenArgs(args);
} else if (isHostPromiseCatch(object)) {
args = wrapHostPromiseCatchArgs(args);
}
}The patch addresses this by implementing a structured normalizer function named normalizeHostPromiseCallbacks that executes recursively when the trap fires. The normalizer checks for Function.prototype.call and Function.prototype.apply indirection and unwinds the call stack up to 64 layers to find the true destination function:
function normalizeHostPromiseCallbacks(object, context, args) {
if (isHost || hostPromiseSanitizeReject === null) return args;
let curFn = object;
let curThis = context;
let container = args;
let base = 0;
let guard = 0;
while (true) {
if (guard++ >= MAX_PROMISE_PEEL) {
throw new VMError(OPNA);
}
if (isHostPromiseThen(curFn)) {
wrapPromiseSlot(container, base, false);
wrapPromiseSlot(container, base + 1, true);
return args;
}
// ... logic continues to peel and sanitize args in place
}
}This normalization prevents sandbox code from hiding the identity of the target behind execution helpers, neutralizing variant bypass methods while protecting against time-of-check to time-of-use (TOCTOU) attacks by snapshotting arguments.
Exploitation requires an async host-realm function exposed to the sandbox that returns a Promise and rejects with an Error containing a custom reference to a host-realm capability. When the sandbox invokes this function, it receives a proxy representation of the Promise.
Instead of registering rejection handlers directly, the exploit invokes the .then or .catch function using Function.prototype.call or Function.prototype.apply. This sequence prevents the proxy from intercepting the callback registration, causing Node's Promise reaction mechanism to invoke the callback with the raw, unsanitized host rejection error:
const p = hostFunction();
p.then.call(p, undefined, (err) => {
// err contains the unsanitized host error object
const hostProcess = err.detail;
hostProcess.mainModule.require('child_process').execSync('id');
});Because the callback operates with direct reference to the raw host error, the attacker accesses the unmitigated host object properties. By navigating properties such as .detail or .cause, the execution context obtains the native process module, resulting in arbitrary execution of system commands.
The security impact of CVE-2026-92937 is critical, achieving the maximum CVSS score of 10.0. A successful exploit breaks all isolation guarantees of the sandbox environment, allowing a remote or local attacker to execute arbitrary shell commands inside the underlying operating system context.
The vulnerability is classified under CWE-94 (Improper Control of Generation of Code) and CWE-693 (Protection Mechanism Failure). Because the sandbox's purpose is to isolate untrusted code, a complete escape represents a total compromise of confidentiality, integrity, and availability on the hosting server.
While there are no current reports of weaponized, active exploits in the wild, public proof-of-concept scripts exist and run reliably. This high severity means any system relying on vm2 to execute untrusted user input is exposed to immediate, full-system takeover if host-provided asynchronous methods return error objects with reference properties.
The primary remediation path is updating the vm2 dependency to version 3.11.7 or later. The patch implements complete callback normalization and argument snapshotting, closing the delivery gap and preventing unmitigated error reference leaks.
However, because the vm2 library is officially deprecated and is no longer maintained, continuing to rely on it poses significant long-term security risks. Organizations must transition away from vm2 to alternative isolation techniques.
Recommended alternatives include WebAssembly sandboxing, running code in dedicated short-lived container environments, or using virtualization hypervisors like gVisor or Firecracker. These solutions provide physical boundary separation at the OS or kernel level, making runtime-level sandbox escapes significantly harder to achieve.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
vm2 patriksimek | <= 3.11.6 | 3.11.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94, CWE-693 |
| Attack Vector | Network |
| CVSS v3.1 Score | 10.0 (Critical) |
| EPSS Score | 0.01033 (1.03%) |
| Exploit Status | Proof of Concept (PoC) available |
| Patch Version | 3.11.7 |
CVE-2026-92938 is a critical sandbox escape vulnerability in the vm2 library (versions 3.11.3 through 3.11.6) that allows arbitrary native code execution on the host when the node:sqlite built-in module is loaded inside a sandboxed NodeVM environment.
CVE-2026-92935 is a critical sandbox escape and remote code execution vulnerability in the vm2 library. By supplying an array or exotic object to the require property of NodeVM while nesting is enabled, attackers can bypass security checks, load the host vm2 module, and run arbitrary shell commands on the hosting server.
CVE-2026-92949 is a sandbox bypass vulnerability in the vm2 library affecting versions 3.9.6 through 3.11.6. The flaw exists due to a breakdown in the ReadOnlyHandler proxy boundary, allowing sandboxed scripts to obtain direct references to wrapped property setters on frozen host-bound objects, ultimately leading to unauthorized state modification in the host environment. This security failure violates the read-only contract enforced by the sandbox for frozen/readonly objects, though it does not by itself allow a full execution-level realm escape. Due to systemic and structural design difficulties in securing a shared-runtime JavaScript sandbox, the vm2 library has been officially deprecated.
A vulnerability in the NodeVM component of the vm2 sandbox package through version 3.11.6 allows sandboxed code to bypass security policies restricting access to built-in modules. When a wildcard require policy is configured with negative deny entries using the 'node:' prefix (e.g., '-node:child_process'), the parser fails to recognize the exemption due to exact string comparison. As a result, the unmitigated module is registered, allowing sandboxed code to import the host child_process module and execute arbitrary system commands.
CVE-2026-92958 is a high-severity sandbox escape and denylist bypass vulnerability within the NodeVM subsystem of the vm2 sandboxing library. When configuring wildcards with negative deny entries, exact-string matches fail to block subpaths like fs/promises. Sandboxed code can import these subpaths to bypass isolation and execute arbitrary filesystem operations on the host.
An incorrect authorization and directory traversal vulnerability in the vm2 library before version 3.11.7 allows remote attackers to bypass the sandbox's external package allowlist. This flaw permits sandboxed code to resolve and execute arbitrary packages available on the host filesystem under host privileges, leading to unauthenticated sandbox escape and host code execution.