September 19, 2026
vm2โs Sandbox Just Failed for the Third Time This Week.
Three separate CVSS 10.0 sandbox escapes landed days apart โ in a library that was declared dead once already

By Vortex 404
4 min read
vm2's Sandbox Just Failed for the Third Time This Week. A Million Weekly Downloads Are Still Running It
Overview
vm2 is a Node.js library that does one job: let your application run someone else's JavaScript without that code being able to touch the host system. It pulls in over a million npm downloads a week and sits behind roughly 885 direct dependents, including real production tools โ n8n's Code node uses a fork of it to sandbox user-submitted scripts in automated workflows. This week, three separate sandbox-escape vulnerabilities landed against it within days of each other, all rated CVSS 10.0, all allowing unauthenticated remote code execution. If that sentence sounds familiar, it's because this is the third time in 2026 that vm2's core promise โ "code in here can't reach out there" โ has broken wide open.
What vm2 is actually promising, and why that promise is hard to keep
vm2 works by wrapping a V8 execution context in a network of JavaScript Proxies that intercept every interaction between sandboxed code and the host โ function calls, property access, error objects, promises. The idea is that no matter what the untrusted code does, it only ever touches proxies, never real host objects. The problem is that this promise depends on the proxy layer catching every single path by which a JavaScript engine can leak a reference to something real, and V8 has a lot of paths. Each of vm2's vulnerabilities this year is a different one of those paths.
The library that was already declared dead once
Context matters here. vm2 had a wave of critical vulnerabilities back in 2023 serious enough that the maintainer deprecated the project outright, from July 2023 to October 2025 โ an explicit "don't use this for security boundaries anymore" statement from the person who built it. It kept shipping anyway: with no real alternative offering the same drop-in API, teams kept pulling it, and it kept racking up downloads through its own deprecation notice. In October 2025 the original maintainer resurrected the project, patched the known issues, and put it back into active development. That's the version of vm2 that's been breaking, repeatedly, ever since.
The 2026 timeline, laid out
- January 2026: CVE-2026โ22709 (CVSS 9.8) โ a gap in Promise callback sanitization let sandboxed code trigger a rejection path that leaked an unwrapped host object back into the sandbox.
- Around May 2026: a single disclosure round added eleven more distinct escape techniques on top of that โ abuse of
__lookupGetter__,util.inspectinternals,DisposableStack/SuppressedErrorhandling, module-loading bypasses, prototype pollution, and a WebAssembly exception-handling trick (CVE-2026-26956, also CVSS 10.0) that used atry_tableinstruction to trigger a TypeError and leak a raw host error object through Symbol-to-string coercion. - September 18โ19, 2026: three more, all CVSS 10.0, all fixed in the same release:
- CVE-2026โ93603 โ vm2's bridge mishandles a nullish
thisreceiver when sandboxed code calls a host function without a receiver (a barefn(),fn.call(),fn.apply(undefined), and similar). V8 substitutes the host's global object asthis, and vm2 hands that object straight to the sandbox as a live proxy - from there,process.getBuiltinModule('child_process').execSyncis reachable. - CVE-2026โ93605 โ vm2's NodeVM keeps a denylist of dangerous built-in modules, but the denylist omits
child_process. Any NodeVM instance configured withbuiltin: ['*']or explicitchild_processaccess hands sandboxed code a straight path to spawn host processes. - CVE-2026โ93606 โ the same Promise-sanitization class of bug as January, found again: overwrite
Symbol.specieson a host Promise, call.then()with no rejection handler, and V8's internal exception thrower re-throws the raw, unwrapped host object into the sandbox.
All three are fixed in vm2 3.12.1.
The pattern matters more than any single CVE
Reading the three most recent bugs individually, none of them looks like negligence โ they're each a genuinely subtle gap in a specific corner of how V8 handles receivers, promises, or module resolution. That's exactly the point. A proxy-based sandbox has to correctly intercept every one of these corners forever, while an attacker only needs to find one that was missed. Fifteen distinct escape techniques disclosed against the same library in nine months isn't a story about one team writing careless code โ it's what happens when the underlying approach requires perfect, permanent coverage of an API surface that wasn't designed to be sandboxed in the first place.
The part worth actually checking today
If you don't know whether anything in your stack uses vm2, that's worth five minutes to find out โ npm ls vm2 (or checking your lockfile) across your services will tell you directly. A few concrete things to check beyond just your own code:
- Upgrade to vm2 3.12.1 or later immediately if you depend on it directly โ all three September CVEs are fixed there.
- If you run n8n, check specifically which vm2 you're on. n8n's Code node has historically used a fork,
@n8n/vm2, and that fork's own npm listing states it hasn't been updated in roughly two years and warns it "contains critical security issues" and "should not be used for production." That's not confirmation the fork is vulnerable to these exact three September CVEs specifically, but it's a strong enough signal that it deserves a direct check against your running version rather than an assumption that an upstream vm2 patch reached you automatically. - Audit anywhere your own code runs user-submitted logic โ plugin systems, low-code/workflow tools, "run custom script" features, chatbot code-execution features โ for whether vm2 (directly or vendored inside a dependency) is the thing standing between that code and your host.
The uncomfortable broader lesson
If your threat model genuinely requires running untrusted code safely, an in-process JavaScript sandbox is the wrong primitive no matter which library implements it โ the guarantee you actually need is a real OS or hypervisor boundary: a container with tightly scoped syscalls, a microVM like Firecracker, or a separate process with minimal privileges, not a set of Proxies inside the same V8 isolate as your application. That's a bigger architectural conversation than "which npm package," but it's the conversation this week's three CVEs are actually pointing at โ the on-going stream of vm2 CVEs isn't a sign the maintainers are bad at their jobs, it's a sign the approach has a ceiling.
Takeaway
vm2 fixed three CVSS 10.0 sandbox escapes in the same release this week, on top of twelve others disclosed earlier this year, in a library that was already declared unsafe once and brought back anyway because nothing else offered the same convenience. If anything in your stack runs untrusted JavaScript through it, patch to 3.12.1 today โ and treat "we sandbox it with vm2" as a statement that needs re-verifying every time a new CVE lands, not a box you checked once.