Running AI-Generated Code Safely: Field Notes on Vercel Sandbox
Headline: Vercel Sandbox runs untrusted code — including code a model just wrote — inside an isolated, ephemeral microVM instead of inside my application's own process. I moved every "let the model write and execute a snippet" feature off ad-hoc child_process calls and onto Sandbox: one sandbox per execution, a hard timeout, an isolated filesystem, and no path back into my app's environment variables. Key takeaways Vercel Sandbox (@vercel/sandbox) runs code inside an isolated Firecracker microVM, not a container in your app's own process — a compromised sandbox can't read your Vercel Function's memory or environment variables. A sandbox is ephemeral: you create one, run commands, read the output, then stop it. There is no persistent state between runs unless you explicitly persist it yourself. 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. The two cases I actually reach for it: an LLM-authored script that needs to run and return a result, and a user-facing "run this code" feature like an AI-generated component preview. Sandbox is not the tool for trusted, first-party build or CI logic — that belongs in the deploy pipeline. Sandbox is for code you did not write and do not trust. Why can't I just run AI-generated code inside my own Vercel Function? A Vercel Function shares its process, filesystem, and environment with the rest of my app. 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 just spin the CPU and starve every other request the function is serving at the same time. I treat any code I did not author myself 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 I can enforce and then throw away. 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: 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 I set per use case, and resources.vcpus controls how much CPU the microVM gets. I 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 I actually execute an LLM-generated snippet inside a sandbox? Write the generated code to a file inside the sandbox, then run it as a subprocess — never pass model output through eval or the Function constructor inside your own function, sandboxed or not. 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 me 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 my app'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. I treat each sandbox as disposable compute, never as a place to store anything. Can I stream a sandbox's output back to the browser while it's running? Yes. runCommand() accepts a detached option, which returns a handle you can read from as output is produced instead of waiting for the whole command to finish — the same pattern I use for streaming a model's token output, just piping a sandbox's stdout instead. 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 Vercel Sandbox Isolation boundary Same process and container as your app Separate Firecracker microVM Filesystem Shares the function's filesystem Isolated, wiped on stop Network egress Shares the function's network context Its own network namespace Blast radius of a crash or hang Can take the whole function down Contained to the sandbox Good for Trusted, first-party subprocess calls you wrote yourself Untrusted, LLM-authored, or user-submitted code When should I not reach for Vercel Sandbox? Not for code I trust and wrote myself — 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. I reach for Sandbox specifically when the code executing is either model-generated or submitted by a user I 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 you provision 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 my environment variables or database? A: Not unless you explicitly pass them in. A sandbox starts with a clean environment, and I 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. I set the shortest timeout the feature can tolerate — seconds for a "run this snippet" playground, longer for batch-style jobs. Q: Should I use Sandbox to run my 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 you did not write: LLM output, user-submitted snippets, anything where the isolation boundary is the point. Originally published on devya.dev. Also on eng-ahmed.com. Built by Devya Solutions.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to