Vercel Sandbox 在 Firecracker microVM 中隔离运行 AI 生成的未信任代码,而非在主进程;每个执行周期独立、硬超时、文件系统隔离且无法访问环境变量,适合 LLM 脚本执行和用户级代码预览。
Vercel Sandbox 运行不受信任的代码——包括模型刚刚生成的代码——是在一个隔离的、临时的 microVM 中运行,而不是在我应用自己的进程内。我把所有"让模型编写并执行代码片段"的功能从临时拼接的 child_process 调用迁移到了 Sandbox:每次执行一个 sandbox、硬超时、隔离的文件系统,且没有路径可以访问到我应用的环境变量。
Vercel Sandbox(@vercel/sandbox)在隔离的 Firecracker microVM 内运行代码,而不是容器,也不是你应用进程内的容器——一个被攻破的 sandbox 无法读取你的 Vercel Function 的内存或环境变量。
Sandbox 是临时的:你创建一个、运行命令、读取输出、然后停止它。除非你显式地持久化,否则两次运行之间没有持久状态。
sandbox.runCommand() 在 sandbox 内执行一个进程,并返回 stdout、stderr 和退出码;sandbox.domain(port) 将一个运行中的服务器暴露在公网 URL 上,用于实时预览。
我实际使用它的两个场景:需要运行并返回结果的 LLM 生成脚本,以及面向用户的"运行此代码"功能(如 AI 生成组件预览)。
Sandbox 不是用于可信的第一方构建或 CI 逻辑的工具——那属于部署流水线。Sandbox 用于你不编写、也不信任的代码。
Vercel Function 与我的应用其他部分共享进程、文件系统和环境。在同一进程内通过 child_process.exec 或更糟糕的 eval 将不受信任的字符串作为代码运行,会将该 Function 能访问的所有密钥——包括 API 密钥和数据库 URL——置于模型生成的任何代码的爆炸半径内。一个生成的代码片段可以读取环境变量、建立出站连接来窃取数据,或者只是耗尽 CPU 并饿死该 Function 同时服务的所有其他请求。
我默认将任何非自己编写的代码视为不受信任的,包括模型按请求生成的代码。不受信任的代码需要自己的计算边界:自己的文件系统、自己的网络上下文,以及我可以强制执行然后丢弃的资源限制。
Vercel Sandbox 为每个 sandbox 调配一个 Firecracker microVM——这是 AWS Lambda 用来隔离租户之间的相同虚拟化技术,不是命名空间或 cgroup 容器。实际区别在于逃脱舱:突破容器意味着跨越工作负载与其邻居共享的内核内的内核命名空间边界,而突破 microVM 意味着找到一个其他任何东西都没有使用的内核的虚拟机监控器级别漏洞。创建一个只需要一次调用:
import { Sandbox } from '@vercel/sandbox';
const sandbox = await Sandbox.create({
runtime: 'node22',
timeout: 60_000, // ms — Vercel 强制停止前的硬上限
resources: { vcpus: 2 },
});
runtime 选择基础镜像,timeout 是我按用例设置的硬上限,resources.vcpus 控制 microVM 获得的 CPU 量。我设置功能能容忍的最短超时,而不是到处复用同一个默认值——代码 eval 游乐场得到秒级,批处理风格的任务得到分钟级。
将生成的代码写入 sandbox 内的文件,然后作为子进程运行它——永远不要将模型输出传入 eval 或你的 Function 构造函数内,无论是否沙箱化。
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() 返回的正是子进程调用会返回的东西:stdout、stderr 和退出码。区别在于该进程实际运行在哪里——在一个一次性的 microVM 内,而不是在我应用的活动密钥旁边。
不。Sandbox 按设计是临时的——每次 Sandbox.create() 调用都会调配一个带有干净文件系统的新 microVM,调用 sandbox.stop() 或达到超时都会完全拆除它,包括写入磁盘的任何内容。如果一个功能需要在多次运行之间记住某些东西——例如多轮代码解释器聊天——那些状态必须存在于 sandbox 外部:从 sandboxed 进程内部将结果写入数据库或 blob 存储,或者持久化一个小清单,调用方下次重新水合到一个新的 sandbox。我将每个 sandbox 视为一次性计算,从不视为存储任何东西的地方。
可以。runCommand() 接受 detached 选项,它返回一个句柄,你可以在输出产生时从中读取,而不是等待整个命令完成——这与我用于流式传输模型 token 输出的模式相同,只是改为传输 sandbox 的 stdout。
const result = await sandbox.runCommand({
cmd: 'node',
args: ['agent.js'],
detached: true,
});
for await (const chunk of result.stdout) {
controller.enqueue(chunk); // 转发到 ReadableStream 响应
}
这些都不需要 edge runtime——将 sandbox 输出通过 Vercel Function 流式传回在默认的 Node.js runtime 上就可以工作,无需额外配置,与流式传输 LLM 响应相同。
不用于我自己信任并编写的代码——与在已经预热的函数中运行子进程相比,调配 microVM 会增加真实的延迟,而且为此付出代价没有隔离收益。也不用于完整的 CI 或构建流水线;那属于平台自己的构建步骤,而不是运行时 sandbox。我专门在执行的代码是模型生成的或由我不信任的用户提交的功能,并且该功能真正需要运行某些东西——子进程、文件系统、任意语言——而不是仅仅调用 LLM API 并返回文本时才会使用 Sandbox。
Q: Vercel Sandbox 和 Vercel Function 是同一个东西吗? A: 不是。Vercel Function 运行你自己部署的代码,拥有你的应用的环境和网络上下文。Sandbox 是一个独立的、临时的 microVM,你在运行时专门调配它来执行你不信任的代码,拥有自己的文件系统且无法访问你 Function 的密钥。
Q: Vercel Sandbox 使用什么隔离技术? A: Firecracker microVM——每个 sandbox 获得自己的内核,而不仅仅是容器命名空间,与 AWS Lambda 用于租户间隔离的隔离级别相同。
Q: sandbox 能访问我的环境变量或数据库吗? A: 除非你显式传入。sandbox 从干净的环境启动,我只会在即将运行不受信任代码的 sandbox 中注入作用域式的、短生命周期的凭证。
Q: sandbox 能运行多长时间? A: 你在创建时设置超时,一旦超时达到 sandbox 就会被强制停止。我设置功能能容忍的最短超时——"运行此代码片段"游乐场是秒级,批处理风格的任务更长。
Q: 我应该用 Sandbox 来运行我自己的构建脚本吗? A: 不——那是部署流水线的职责。Sandbox 赚回成本的地方——microVM 调配延迟和无持久状态——专门用于非自己编写的代码:LLM 输出、用户提交的代码片段,以及任何隔离边界才是重点的场景。
Originally published on devya.dev. Also on eng-ahmed.com. Built by Devya Solutions.