Running AI-Generated Code Safely: What We Learned Wiring Up Vercel Sandbox
Table of Contents
About the Author

Ahmed Mahmoud
Author & Developer
Software engineer passionate about web development and user experience design.
Founder of Devya · eng-ahmed.com ↗Key takeaways
Vercel Sandbox (`@vercel/sandbox`) runs code inside an isolated Firecracker microVM, not a container in our application's own process — a compromised sandbox can't read our Vercel Function's memory or environment variables.
A sandbox is ephemeral: we create one, run commands, read the output, then stop it. There is no persistent state between runs unless we explicitly persist it ourselves.
`sandbox.runCommand()` executes a process inside the sandbox and returns stdout, stderr, and an exit code; `sandbox.domain(port)` exposes a running server on a public URL for a live preview.
We reach for Sandbox in two situations: an LLM-authored script that needs to run and return a result, and a user-facing "run this code" feature such as an AI-generated component preview.
Sandbox is not the right tool for trusted, first-party build or CI logic — that belongs in the deploy pipeline itself. Sandbox is for code we did not write and do not trust.
Why can't we just run AI-generated code inside our own Vercel Function?
A Vercel Function shares its process, filesystem, and environment with the rest of our application. Running an untrusted string as code in that same process — through `child_process.exec` or, worse, `eval` — puts every secret the function can see, API keys and database URLs included, inside the blast radius of whatever the model wrote.
A generated snippet can read environment variables, open an outbound connection to exfiltrate them, or simply spin the CPU and starve every other request the function is serving at the same time.
At Devya we treat any code we did not author ourselves as untrusted by default, and that includes code a model generates on request. Untrusted code needs its own compute boundary: its own filesystem, its own network context, and resource limits we can enforce and then discard.
What is Vercel Sandbox actually running under the hood?
Vercel Sandbox provisions a Firecracker microVM for every sandbox — the same virtualization technology AWS Lambda uses to isolate tenants from each other, not a namespace or cgroup container.
The practical difference is the escape hatch: breaking out of a container means crossing a kernel-namespace boundary inside a kernel the workload shares with its neighbors, while breaking out of a microVM means finding a hypervisor-level exploit against a kernel nothing else is using.
Creating one is a single call:
```ts import { Sandbox } from '@vercel/sandbox'; const sandbox = await Sandbox.create({ runtime: 'node22', timeout: 60_000, // ms — hard ceiling before Vercel force-stops it resources: { vcpus: 2 }, }); ```
`runtime` picks the base image, `timeout` is a hard ceiling we set per use case, and `resources.vcpus` controls how much CPU the microVM gets. We set the shortest timeout a feature can tolerate rather than reusing one default everywhere — a code-eval playground gets seconds, a batch-style job gets minutes.
How do we actually execute an LLM-generated snippet inside a sandbox?
We write the generated code to a file inside the sandbox, then run it as a subprocess — model output never passes through `eval` or the `Function` constructor inside our own function, sandboxed or not.
```ts await sandbox.writeFiles([ { path: 'snippet.js', content: Buffer.from(generatedCode) }, ]); const result = await sandbox.runCommand({ cmd: 'node', args: ['snippet.js'], }); const stdout = await result.stdout(); const exitCode = result.exitCode; ```
`runCommand()` gives us back exactly what a subprocess call would: stdout, stderr, and an exit code. The difference is where that process actually ran — inside a disposable microVM instead of next to our application's live secrets.
Does a Vercel Sandbox keep state between runs?
No. Sandboxes are ephemeral by design — each `Sandbox.create()` call provisions a fresh microVM with a clean filesystem, and calling `sandbox.stop()`, or hitting the timeout, tears it down completely, including anything written to disk.
If a feature needs to remember something across runs — a multi-turn code-interpreter chat, for instance — that state has to live outside the sandbox: write results to a database or blob store from inside the sandboxed process, or persist a small manifest the caller rehydrates into a new sandbox next time. We treat each sandbox as disposable compute, never as a place to store anything.
Can we stream a sandbox's output back to the browser while it's running?
Yes. `runCommand()` accepts a `detached` option, which returns a handle we can read from as output is produced instead of waiting for the whole command to finish — the same pattern we use for streaming a model's token output, just piping a sandbox's stdout instead.
```ts const result = await sandbox.runCommand({ cmd: 'node', args: ['agent.js'], detached: true, }); for await (const chunk of result.stdout) { controller.enqueue(chunk); // forward to a ReadableStream response } ```
None of this needs the edge runtime — streaming a sandbox's output back through a Vercel Function works on the default Node.js runtime with no extra config, the same as streaming an LLM response.
What's the actual difference between Sandbox and child_process in a Function?
child_process in a Function
Isolation boundary: same process and container as the app. Filesystem: shares the function's filesystem. Network egress: shares the function's network context. Blast radius of a crash or hang: can take the whole function down. Good for: trusted, first-party subprocess calls we wrote ourselves.
Vercel Sandbox
Isolation boundary: separate Firecracker microVM. Filesystem: isolated, wiped on stop. Network egress: its own network namespace. Blast radius of a crash or hang: contained to the sandbox. Good for: untrusted, LLM-authored, or user-submitted code.
When should we not reach for Vercel Sandbox?
Not for code we trust and wrote ourselves — provisioning a microVM adds real latency compared to a subprocess in an already-warm function, and there's no isolation benefit to pay that cost for.
Not for a full CI or build pipeline either; that belongs in the platform's own build step, not a runtime sandbox. We reach for Sandbox specifically when the code executing is either model-generated or submitted by a user we don't trust, and the feature genuinely needs to run something — a subprocess, a filesystem, an arbitrary language — rather than just call an LLM API and return text.
FAQ
Q: Is Vercel Sandbox the same thing as a Vercel Function?
A: No. A Vercel Function runs your own deployed code with your app's environment and network context. A Sandbox is a separate, ephemeral microVM provisioned at runtime specifically to execute code you don't trust, with its own filesystem and no access to your function's secrets.
Q: What isolation technology does Vercel Sandbox use?
A: Firecracker microVMs — each sandbox gets its own kernel, not just a container namespace, the same class of isolation AWS Lambda uses between tenants.
Q: Can a sandbox access our environment variables or database?
A: Not unless we explicitly pass them in. A sandbox starts with a clean environment, and we only ever inject scoped, short-lived credentials into a sandbox that's about to run untrusted code.
Q: How long can a sandbox run?
A: You set a timeout when you create it, and the sandbox is force-stopped once that timeout hits. We set the shortest timeout the feature can tolerate — seconds for a "run this snippet" playground, longer for batch-style jobs.
Q: Should we use Sandbox to run our own build scripts?
A: No — that's what the deploy pipeline is for. Sandbox earns its cost, microVM provisioning latency and no persistent state, specifically for code we did not write: LLM output, user-submitted snippets, anything where the isolation boundary is the point.
Further Reading
AI Engineering
Getting Typed JSON Out of LLMs: How We Use generateObject and the AI SDK
Language models return strings, not types. These are the Vercel AI SDK patterns our team uses to get schema-validated JSON out of a model every time: generateObject for extraction, streamObject for progressive UI, the four output modes, and NoObjectGeneratedError as a real code path.
AI Engineering
One API Key for Every Model: How We Use the Vercel AI Gateway
We removed three provider SDKs and three API keys after routing our AI SDK calls through the Vercel AI Gateway. Here is how string-based model routing works, when fallbacks earn their keep, and what the gateway actually replaces.