Skip to main content

13 Critical CVEs, One Patch: What the vm2 Sandbox-Escape Wave Says About Isolating AI Agents at the Wrong Layer

8 min readDora NodaDora Noda
Share
On this page

On May 1, 2026, the maintainers of vm2 — a Node.js library that had been publicly declared "no longer maintained" back in 2023 — shipped version 3.11.0 alongside thirteen security advisories. Several carried a CVSS score of 9.8 or a perfect 10.0. Every one of them was a sandbox escape: an attacker who could feed arbitrary code into a vm2 VM.run() call could walk straight out of the sandbox and execute commands on the host. The library still pulls roughly 16 million downloads a month, much of it inside tools that run untrusted, LLM-generated code.

That's the headline number. The more useful question is why one already-abandoned library produced thirteen distinct escape primitives in a single disclosure wave, instead of one bug that got fixed. The answer is architectural, not incidental — and it's the clearest real-world argument yet for why isolating AI-agent code execution has to happen below the language runtime, not inside it.

What Actually Broke

vm2's entire security model lives inside JavaScript itself. It works by transforming code to inject handleException() calls into every catch clause, and by wrapping every object that crosses the sandbox boundary in a "Bridge Proxy" that's supposed to intercept access to dangerous host internals before they leak. Both mechanisms are pure JavaScript, running in the same V8 process, the same OS process, as the code they're supposed to contain.

CVE-2026-26956 (CVSS 9.8) shows exactly where that falls apart. WebAssembly's try_table instruction with a JSTag catch handler intercepts JavaScript exceptions at V8's C++ level — a layer that sits below JavaScript entirely. When sandboxed code triggers a TypeError through a Symbol-to-string coercion during stack formatting, the WASM exception handler catches it as an opaque externref and hands it back as a plain function value, never touching vm2's handleException() instrumentation at all. Because that leaked object originates from the host realm, its constructor chain leads straight back to Node's process object — and from there, arbitrary command execution. A separate advisory in the same wave, CVE-2026-43997, reaches the same outcome through a different route: obtaining a reference to the host Object directly and using it to escape.

Different bugs, same structural cause. vm2's protections are checks written in JavaScript, and JavaScript is not the only thing running inside a V8 process — WASM exception handling, Promise internals, and Proxy trap edge cases all operate at a layer vm2's transformer never touches. That's why the May wave was thirteen advisories and not one: once you understand that any sandbox implemented in the language it's sandboxing can be bypassed by anything operating below that language, finding the next bypass becomes a matter of which subsystem you probe next, not whether one exists.

The Part That Should Actually Worry a Platform Team

None of this is theoretical anymore, and not because of a human red team. In March 2026, a joint team from the University of Oxford and the UK AI Security Institute published SandboxEscapeBench — 18 container-breakout scenarios spanning orchestration misconfigurations, container-runtime bugs, and kernel-level privilege escalation chains, built specifically to measure whether frontier LLMs can find and chain these exploits themselves, safely, in a controlled setting.

The results: GPT-5 and Opus 4.5 both hit a 0.40 success rate on the benchmark's "level 3" scenarios (grounded in real runtime CVEs and misconfigurations, not toy examples), for roughly one dollar per attempt. Zero successes on levels 4 and 5, which require multi-step kernel exploitation — the frontier models aren't there yet. But the research team also reported something more unsettling than the success rate: the models discovered four escape paths the researchers hadn't anticipated going in, including exploiting default Vagrant SSH credentials to route around a container entirely instead of breaking out of it.

Put the two data points together. vm2 shows that a JavaScript-level sandbox produces double-digit distinct bypasses once anyone looks closely — a known, unglamorous engineering fact security researchers have understood for years. SandboxEscapeBench shows that "anyone looks closely" is no longer a bottleneck gated by human attacker time; a frontier model will do it autonomously, for about a dollar, and occasionally find a path nobody wrote a scenario for. An AI coding agent that executes generated code inside a language-level sandbox isn't just exposed to a known-bad library — it's exposed to an attacker (or, more likely, its own hallucinated or prompt-injected commands) that can now probe that exposure at machine speed.

Why "Better JavaScript Sandbox" Doesn't Fix It

The vm2 maintainers themselves recommended isolated-vm as the replacement back in 2023, and it's a real improvement: separate V8 isolates mean a separate heap and no shared prototype chain, closing off the exact kind of object-leak vm2 suffered from. But isolated-vm still runs inside the same OS process as the host application, using a native C++ addon to manage the isolate boundary. A bug in that native layer, or a V8 engine vulnerability that escapes an isolate rather than a vm2 sandbox, still lands on the same process, same filesystem, same credentials as everything else running there. It moves the escape hatch; it doesn't remove the building it's attached to.

Kernel-level isolation is a different category of guarantee, and the difference is exactly the layer where vm2 failed. gVisor runs as a userspace kernel that intercepts every syscall the sandboxed process makes, so even full control over the process inside the sandbox still has to get past a syscall filter before touching the real kernel. Kata Containers goes further, giving each sandbox its own dedicated kernel via lightweight hardware virtualization (QEMU-backed microVMs) — a full escape means a hypervisor breakout, not a language-runtime bug. Neither cares whether the code running inside is JavaScript, Python, or a binary; the isolation boundary sits below the language entirely, in the same place SandboxEscapeBench's "level 4 and 5" scenarios live — the ones no frontier model has cracked yet.

This is also, not coincidentally, the exact split Kubernetes' own Agent Sandbox project made explicit at its KubeCon NA 2025 launch: a declarative controller for isolated, stateful sandbox pods that supports both gVisor (the default) and Kata Containers as interchangeable runtimeClassName backends, letting a platform pick isolation strength per workload without changing anything about how the sandbox is provisioned.

The two backends aren't interchangeable in cost, and that tradeoff is worth naming rather than waving off. gVisor's syscall interception adds real overhead on syscall-heavy workloads — file I/O and networking in particular — because every syscall the sandboxed process makes has to round-trip through the userspace kernel before it reaches the real one. Kata's per-sandbox microVM buys a materially stronger guarantee (a dedicated kernel, not an intercepted one) at the cost of a heavier boot: a fresh QEMU-backed VM takes longer to schedule than a gVisor-wrapped container, which matters if an agent's tool-call loop is spinning up a sandbox per shell command rather than reusing one for a whole session. Neither number makes the case for skipping kernel-level isolation — both are still a different security category than vm2's shared-process model — but a platform choosing between them for an agent-execution workload should size the choice to the actual call pattern (short-lived probe versus a long debugging session) instead of defaulting to whichever backend shows up first in a tutorial.

What This Means for a Platform Actually Running Agent Code

A self-hosted PaaS whose agent surface lets an AI agent execute shell commands, run generated code, or operate a deploy has to make exactly one decision correctly: what's the trust boundary, and what layer is it enforced at. The vm2 wave is thirteen concrete, dated, CVSS-scored data points that the answer isn't "a well-reviewed JavaScript sandbox library" — not because vm2 in particular was sloppy, but because any protection implemented in the same language and process as the code it's containing is provably bypassable by something operating one layer down, and the 2026 disclosure wave is what that looks like when someone finally goes looking.

The alternative isn't exotic. It's the same primitive Kubernetes' Agent Sandbox project ships today: gVisor or Kata Containers as the runtime class, a syscall boundary or a dedicated kernel between the agent's code and the host, provisioned on infrastructure the platform already runs — a Cluster API-managed node pool is enough to schedule sandbox pods with the right runtimeClassName, no separate vendor integration required. A JavaScript VM should never be the last line of defense between an AI agent's generated code and a host that also happens to be running a tenant's production workload.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Sources

All CVE identifiers, CVSS scores, and benchmark figures above are drawn directly from the linked sources.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex