Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui, itaque voluptate ipsa non enim amet ducimus voluptatibus deserunt nam esse!
Testing AI-Generated Code? vm2 Escapes Prove Sandbox Isolation Is Still Broken

Testing AI-Generated Code? vm2 Escapes Prove Sandbox Isolation Is Still Broken

pr0h0
securityjavascriptsandboxingai-security
AI Usage (91%)

Why vm2 escapes matter for AI-generated code

The point of vm2 is simple: run untrusted JavaScript without handing it the host process. The problem is that a sandbox is only useful if it actually holds up under attack. Recent vm2 escape activity is another reminder that sandbox isolation for JavaScript is still a fragile promise, especially when products execute AI-generated code, customer scripts, or plugin logic.

That makes this issue relevant for SaaS runners, browserless automation, internal tooling, and AI workflows that treat generated code as safe enough to execute. If the boundary is weak, untrusted code can move from “just a snippet” to a path toward secrets, network access, or host-level control.

What vm2 is trying to protect

vm2 exists to execute code with less access than the surrounding Node.js app. In practice, teams use it for calculations, transforms, automation hooks, or policy logic that should not touch the filesystem, process APIs, or environment secrets.

That model looks clean on paper, but JavaScript was not built around hard security boundaries. Host objects can leak behavior, async edges cross contexts, and wrapper APIs often carry more power than the code owner expects. Once the sandbox exposes a bridge to host state, the isolation story gets thin fast.

The trust model usually looks like this:

LayerWhat teams hopeWhat they should assume
Sandbox runtimeUntrusted code stays containedThe sandbox may be escaped
Host bridgeOnly safe helpers cross the boundaryHelper objects can become leverage
App logicThe user only runs their own codeThe code may reach secrets or tools

How vm2 escapes break the boundary

A vm2 escape does not have to be dramatic to be damaging. If attacker-controlled code can influence host objects, trigger a bad error path, or exploit object behavior across an async boundary, the sandbox stops being a real defense.

Untrusted code is not the same as isolated code

A common pattern is easy to miss:

  1. Accept user code or ai-generated code.
  2. Run it in vm2.
  3. Expose helper functions for logging, HTTP, or storage.
  4. Assume the helpers are safe because they are “inside the sandbox.”

That last step is where things go wrong. If the helper touches host state, then a sandbox bug can turn into host compromise. In a code runner, that might mean reading environment variables. In an AI workflow, it might mean reaching the token cache or internal service client used by the orchestrator.

Host objects, async edges, and prototype behavior

The risky parts usually show up when the sandbox and host share more than plain data. Common danger zones include:

  • callback APIs that cross the boundary
  • error objects passed back to the host
  • Promise chains and async hooks
  • convenience wrappers around fetch, fs, or shell execution
  • serialization logic that trusts object shape too much

The lesson is boring, but it keeps showing up: JavaScript object boundaries are not security boundaries.

Where this shows up in real products

SaaS plugins and customer script runners

This is the obvious case. A platform lets users write custom automation scripts for billing, support, or operations. The scripts are “isolated” in vm2, but the platform still gives them access to APIs, secrets, or internal service clients.

If vm2 escapes, the attacker is no longer limited to their own tenant. Impact can include tenant-to-tenant data access, secret exposure, or privileged internal requests.

AI workflow tools and code-generation agents

This is the newer version of the same mistake. A code assistant generates a script, then the product executes it to “help the user.” If that generated code runs in a sandbox that can be escaped, the model output becomes an execution vector.

The risk is not that the model is malicious. The risk is that people trust generated code enough to wire it to credentials and internal tools.

Internal developer platforms

Teams also do this for convenience: preview environments, workflow runners, policy engines, and automation jobs often execute JavaScript under the assumption that internal users are trusted. They are not always malicious, but their inputs can still be compromised.

A sandbox escape here can turn a low-friction dev tool into an internal pivot point.

A safe way to test your own vm2 usage

Minimal boundary check

You do not need a dangerous payload to validate the architecture. Start by testing whether your app treats the sandbox as a hard boundary.

vm2-boundary-check.js
const { VM } = require("vm2");

const vm = new VM({
timeout: 1000,
sandbox: {
  helper: {
    echo(value) {
      return value;
    }
  }
}
});

const result = vm.run(`
helper.echo("hello");
`);

console.log(result);

Then inspect what else is reachable through the same integration points. Ask:

  • Can sandboxed code influence host callbacks?
  • Are errors logged with stack traces that expose internals?
  • Do helper objects leak methods tied to process state?
  • Is any secret ever present in the same process?

What to log and verify

I usually test three things:

  • whether the sandbox can read unintended globals
  • whether host APIs remain reachable through wrappers
  • whether the host process can be influenced after a thrown error or rejected promise

Log the following:

  • every host object exposed to the sandbox
  • every network call made during execution
  • every file or environment access path
  • the exact permissions granted to the runner

If your monitoring cannot answer “what did this code touch,” the sandbox is already too trusted.

Defensive controls that matter more than vm2 alone

Process isolation and OS-level boundaries

Use vm2 as a convenience layer, not a containment strategy. Real isolation means separate processes, locked-down containers, seccomp or equivalent restrictions, and a killable runtime.

If the code is risky enough to run, it is risky enough to isolate at the OS level.

Network, filesystem, and secret restrictions

The most important control is not clever syntax filtering. It is denying access:

  • no ambient environment secrets
  • no default filesystem visibility
  • no outbound network unless explicitly required
  • no shared token cache
  • no direct access to cloud metadata endpoints

Permission review and runtime monitoring

Treat execution permissions like application permissions. Review them, scope them, and monitor them.

Watch for:

  • newly exposed tool permissions
  • scripts that suddenly request broader access
  • repeated execution failures that may indicate probing
  • unusual network destinations during sandboxed runs

What to tell product and engineering teams

The message is simple: if your product runs untrusted JavaScript, vm2 is not the boundary you should advertise.

Tell teams to:

  • pin and review dependencies
  • assume sandbox escapes will happen
  • isolate execution at the process and container level
  • keep secrets out of the runtime
  • restrict network and filesystem access by default
  • audit every tool or plugin permission
  • monitor install-time and execution-time behavior

That advice is not specific to vm2. It is the same advice that applies to AI packages, developer tools, and anything else sitting close to credentials and production systems.

Further Reading

Share this post

More posts

Comments