Show HN:Plimsoll,一个自托管沙箱,用于运行 AI 代理编写的代码
摘要
Plimsoll 是一个自托管沙箱工具,用于运行 AI 代理编写的不可信代码,并在每次结果中明确标注隔离强度(process/container/kernel/vm),支持最低隔离层级要求、运行记录哈希校验与签名验证。
查看缓存全文
缓存时间: 2026/10/12 00:43
plimsollmark/plimsoll
Source: https://github.com/plimsollmark/plimsoll
plimsoll
plimsoll 在沙箱中运行不受信任的代码,例如 AI 代理编写的代码,并且每个结果都会说明那个沙箱有多强。请求可以要求一个最低强度;如果沙箱强度不足,什么都不会运行。
audit (https://github.com/plimsollmark/plimsoll/actions/workflows/audit.yml) gvisor (https://github.com/plimsollmark/plimsoll/actions/workflows/gvisor.yml) Go Reference (https://pkg.go.dev/github.com/plimsollmark/plimsoll) Go version release (https://github.com/plimsollmark/plimsoll/releases) license
名称来自 Plimsoll 线(载重线),即绘制在船体外部的载重上限,任何人都能查看。plimsoll 对隔离层级做同样的事:一次运行周围的围墙有多强,以四个层级之一表示,由弱到强依次为:process、container、kernel、vm。层级是每个结果上的一个值,而不是产品规格书里的一句话。请求可以设置一个下限,即它能接受的最弱层级,而官方 Go 客户端在结果返回时会再次检查层级。同一套机制让 plimsoll 不只能隔离代理编写的代码,还能对它进行考核。
内置在仿真镜像中的试运行器会针对代理编写的程序读不到的场景来运行该程序:每个场景都以一个在程序启动前即被删除的文件形式送达运行器。
每次运行都会返回请求发出时与结果返回时的 SHA-256 哈希,官方客户端会重新计算(方法)。守护进程之外还有一个独立程序 plimsoll-attest,它对已检查的结果签名,拒绝调用链中缺失任何一环的情况,并能重放一次运行。
考核本身(仿真系统、场景、及格线)仍归你所有:plimsoll 提供的是边界与证据。
看一次真实运行
一个 AI 代理编写了一个控制器:一个在每个节拍(此处为仿真的一个 10 毫秒步长)读取系统状态、并决定如何推动它的程序。plimsoll 让它在一个仿真的*车摆(cart-pole)*上运行——一辆在轨道上的小车,顶部铰接一根杆子,控制器通过左右推动小车使杆子保持直立。
模拟器被编译为 WebAssembly,一种可在宿主程序中运行的可移植字节码,并构建进沙箱镜像里。调用方通过常规项目 API 只发送了控制器这一个文件,它在 container 层级上运行。
物理神谕:由代理编写的控制器平衡在小车上的杆子,旁边是每次运行轨迹的 SHA-256 指纹 (https://plimsollmark.github.io/plimsoll/examples/oracle/index.html)
车摆是控制工程中的标准教学问题。运行器——构建在镜像中、负责让控制器与模拟器一起运行的程序——记录运行的轨迹,即每个节拍下的每个状态,并把它哈希为指纹:一个 SHA-256 哈希,因此相同的指纹意味着完全相同的数值。
把被接受的控制器运行两次,两个指纹一致。代理的第一版草稿中有两个增益(其公式里的乘数)符号错了:它在 5.96 秒时让杆子倒下,而且指纹不同。
二十秒内杆的角度与小车位置,分别是被接受的控制器和倒下的草稿 (https://plimsollmark.github.io/plimsoll/examples/oracle/index.html)
打开实时运行报告 ↗ (https://plimsollmark.github.io/plimsoll/examples/oracle/index.html),在浏览器中重放这两次运行、阅读代理编写的控制器,并看看该页面刻意没有声称什么。
一次示例执行会产生三次沙箱运行:被接受的控制器两次,草稿一次。用 make docker-images && go run ./examples/oracle 复现它们。
它做的两件事
它报告围墙。 每个结果都说明该运行实际获得的隔离层级。每个请求都可以设置下限,而守护进程满足不了其下限的请求会在任何代码运行之前就被拒绝:ErrInsufficientIsolation 意味着什么都没有运行。
res, err := provider.Sandbox.RunJavaScript(ctx, sandbox.Request{
Code: "console.log(6 * 7)",
MinimumIsolation: sandbox.IsolationVM,
// ErrInsufficientIsolation 意味着什么都没有运行
})
// res.Isolation 报告的是实际运行所处的边界。
每一次什么都没运行的拒绝都会被打上这样的标记,并附一个原因(request、permission、protocol、unsupported、isolation、environment、capacity)。sandbox.NotDispatchedReason(err) 会读出这个标记,无论provider(运行代码的后端)是在你自己的进程里还是在守护进程后面。没有该标记的错误可能发生在代码已经开始之后,因此永远不要自动重试。
标记只在带着客户端请求 ID 返回的回答上才算数,这样用一个请求的回答去应答另一个请求的代理,就无法让一次真正运行过的调用读作“什么都没运行“(返回内容及其含义)。
该层级是守护进程收集到的证据:它的配置以及它自身的启动检查。它不是远程证明(attestation)——即来自硬件的、关于正在运行什么软件的密码学证明——而且这里没有任何 provider 提供这一点。docs/isolation-tiers.md 列出了每个层级各自依据什么。
它让代理编写的代码在从未持有你的凭证、你的 API 地址或开放网络访问的情况下调用你的 API。 一个 grant 授予一次运行调用某个 API 上列出路由的权限。代码通过 plimsoll 放进沙箱的一个小客户端来调用,而 broker——plimsoll 位于沙箱之外、负责发出真实调用的部分——会检查调用方和精确路由,然后由它自己附加凭证。凭证是按次铸造的:plimsoll 每次运行只索取一次,因此它可以是一个新鲜的短期令牌,并且它永远不会进入沙箱。跳过客户端、手工向外调用的代码走不了多远,因为执行规则的是 broker 而不是客户端。参见 docs/capability-grants.md。
从代理框架使用它
以下代理框架可以通过自己的工具或代码执行器,把模型生成的代码交给 plimsoll(其中的 Trigger.dev 是一个托管的 TypeScript 任务运行器)。每一行都标明了要安装什么、指南在哪,以及一个重放该框架的工具从一次调用到沙箱再返回的页面,拒绝也包括在内。
| Framework | Package or extra | Guide | See a run |
|---|---|---|---|
| Vercel AI SDK | @plimsollmark/client/ai-sdk | Vercel AI SDK guide (INTERNAL · documentation →) | Vercel AI SDK page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/vercel-ai-sdk/index.html) |
| Google ADK | plimsoll-client[adk] | Google ADK guide (INTERNAL · documentation →) | Google ADK page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/google-adk/index.html) |
| Agno | plimsoll-client[agno] | Python client guide, Agno and CrewAI tools (INTERNAL · documentation →) | Agno page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/agno/index.html) |
| CrewAI | plimsoll-client[crewai] | Python client guide, Agno and CrewAI tools (INTERNAL · documentation →) | CrewAI page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/crewai/index.html) |
| Trigger.dev | @plimsollmark/client/trigger | Trigger.dev guide (INTERNAL · documentation →) | Trigger.dev page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/trigger-dev/index.html) |
| Mastra | @plimsollmark/client/mastra | TypeScript client guide, Mastra (INTERNAL · documentation →) | Mastra page (INTERNAL · plimsoll site →) (https://plimsollmark.github.io/plimsoll/integrations/mastra/index.html) |
给 Trigger.dev 代理一个代码工具
TypeScript 附加包为 Trigger.dev 上的聊天代理提供一个 executeCode 工具。在守护进程上有一个会话(为多次调用保留的一个沙箱)时,每次调用就是一个 cell,即在为该对话保留的解释器里运行的代码:变量和文件可以存活到下一次调用。每个工具结果都会说明它们能否存活、这次调用是否是全新开始、它在什么隔离层级之后运行,以及运行记录的 SHA-256——一份关于发送了什么、返回了什么的收据,客户端在返回之前已经校验过。
调用方可以设置一个最低层级,于是较弱的 provider 会在运行代码之前就拒绝。确切的保证和生产环境的最低要求请参见功能与部署指南。
可部署的 Trigger.dev 起步项目 (https://github.com/plimsollmark/plimsoll-trigger-starter) 带有一个聊天代理,以及一个任务,用来在真实守护进程上证明两个 cell 共享同一个解释器,而无需调用任何 AI 模型。
一分钟内运行点东西
这个示例不需要守护进程、不需要 docker、不需要凭证、也不需要网络:它使用 wasm provider,在你自己的进程内、在一个 QuickJS(一个编译为 WebAssembly 的小型 JavaScript 引擎)上运行 JavaScript。克隆仓库和拉取 Go 模块确实会用到网络。
git clone https://github.com/plimsollmark/plimsoll && cd plimsoll
go run ./examples/minimal
provider wasm
isolation process
exit code 0
timed out false
truncated stdout=false stderr=false
duration 304ms
stdout {"Engineering":59000000,"Sales":20300000,"Operations":9800000}
isolation 那一行来自运行的结果,而不是示例自己写出来的文字。process 意味着根本没有操作系统层面的围墙:不要用它来跑有敌意的代码。
同一条命令在装好 gVisor 之后会以 kernel 层级运行。gVisor 是容器与你机器内核之间的一层,它代为处理容器发给操作系统本身的请求,因此代码从不直接与你的内核对话。sudo ./docker/install-gvisor.sh 会安装一个固定版本的 gVisor 发行版(只有一个确切版本,只能通过修改本仓库来变更),并把它的运行时 runsc 注册给 docker。程序的其他部分没有任何变化:
make docker-images # 构建项目镜像(plimsoll/sandbox),docker provider 会检查它
SANDBOX_PROVIDER=docker SANDBOX_DOCKER_RUNTIME=runsc go run ./examples/minimal
在不设置 SANDBOX_PROVIDER 的情况下,plimsoll 使用 Disabled provider,它什么都不运行,因此代码执行绝不会被意外打开。
下一步: docs/getting-started.md 在同一台机器上走一遍同样的各个部分:启动守护进程、创建一个调用方凭证、编写你自己的客户端、看一次下限如何拒绝运行,然后在不改客户端的情况下把守护进程从 process 提升到 kernel。它也涵盖了如何内嵌 Go 包而不是运行守护进程。
各组件的位置
flowchart LR
A["代理或 MCP 网关(MCP:AI 应用如何向模型提供工具)"] -->|"运行:一个载荷、一个协议编号、minimum_isolation"| AU
subgraph D["plimsolld"]
AU["验证调用方"] --> FL["把下限与当前 provider 的证据比较"]
FL --> DI["分发"]
BR["broker:持有铸造的凭证、匹配精确路由、统计每一次调用"]
end
DI --> W["wasm:QuickJS 于 wazero(process 层级)"]
DI --> K["docker 配 runsc(kernel 层级)"]
DI --> V["e2b Firecracker(vm 层级)"]
W -. "host.get / host.post" .-> BR
K -. "每次运行一个 Unix 套接字" .-> BR
V -. "经过验证的 guard" .-> BR
BR ==>|"你的凭证,在宿主侧附加"| API["你的 API"]
沙箱里的代码,即 guest,从不持有凭证,也从不直接触达网络。一次没有 grant 的运行完全没有任何网络。
隔离层级
plimsolld 是 plimsoll 的服务器。每个实例只运行一个 provider,由 SANDBOX_PROVIDER 选择:
| Provider | 边界 | 报告的层级 | 用途 |
|---|---|---|---|
wasm | 运行在 wazero(一个用 Go 编写的 WebAssembly 运行时)之上的 QuickJS,位于 plimsolld 自身内部 | process | 快速的本地开发。引擎中的逃逸(让代码逃出沙箱的漏洞)会落在你自己的守护进程里。 |
docker 配 runc(docker 的默认运行时) | 与你的机器共享内核的容器 | container | 当内核漏洞不在你打算防范的攻击之列时的自托管方案。配合项目镜像它可以保持会话:一个容器服务多次调用,内置一个 JavaScript 解释器(以 plimsoll/sandbox-python 作为项目镜像时还有一个 Python 解释器),其变量在调用之间保留。 |
docker 配 runsc | gVisor,前提是启动检查确认 docker 已注册 runsc | kernel | 在你自己的机器上跑有敌意的代码。同样保持会话。 |
e2b | 来自 E2B 的 microVM(为一次运行而创建、随后销毁的小型虚拟机),E2B 是在 Firecracker(AWS 的开源虚拟机监控器)上运行它们的托管服务 | vm | 有敌意的代码,运行在 E2B 的机器上而非你的机器;按次计费。同样保持会话,在 microVM 运行期间计费;挂起会暂停它,文件保留,但解释器的变量不保留。 |
dockercloud | 来自 Docker Cloud Sandboxes(Docker 的托管沙箱服务)的 microVM | vm | 有敌意的代码,运行在 Docker 的机器上;按次计费。它有两套 API,由 SANDBOX_DOCKERCLOUD_API 选择,两套都在 2026-10-04 通过了实时测试套件:一套是 Docker 在发布前放出、现已不再有文档的 API,它是默认选择,因为只有它具备 grant 和镜像证据;另一套是 Docker 有文档的 REST API,作为备选保留,直到它覆盖这些能力(没有 grant、在 PLIMSOLL_HARDENED=1 下被拒绝、单次运行最长 270 秒)。该账户的网络策略必须是全拒绝(除非有规则允许,否则不允许任何连接),每次运行都会对此进行检查。当设置了 SANDBOX_DOCKERCLOUD_GUARD_URL 时,grant 通过与 E2B 相同的 guard(plimsoll 服务器上的一个地址,microVM 唯一可以连接的地方)抵达 broker。与 E2B 不同的是,guest 持有自己那一次运行的 guard 短期凭证,而那条唯一的网络规则是通过其公开 API 之外的一次 Docker 调用设置的。 |
openshell | 来自 OpenShell(NVIDIA 的代理沙箱运行时)的沙箱,由其网关服务器在 docker 上创建 | container | 已经在运行 OpenShell 网关的代理平台。每次运行都会获得自己的无网络沙箱;plimsoll 会回读它的设置,发现任何差异便拒绝运行,并在事后删除它。于 2026-09-28 针对 v0.1.2 网关测试过。grant 通过沙箱内的一个中继抵达 broker,plimsoll 从外部连接这个中继,因此沙箱不需要任何网络规则。同样保持会话。 |
| 未设置 | 什么都不运行 | n/a | 默认值。 |
每个层级都依赖于守护进程的配置、provider 报告的内容,以及启动时的一次真实测试运行;没有一个是远程证明。docs/isolation-tiers.md 列出了每个层级的证据,并演示请求如何设置自己的下限。
会话用每次调用的新鲜沙箱换取速度和保留的状态:先前某次调用运行过的代码可以改变后续调用看到的东西,而一次带 API 访问权限的调用需要一个允许会话的 grant(docs/sessions.md)。一个会话就是一个信任域,plimsoll 把它绑定到经过验证的调用方,而不是该调用方的客户:一个用同一份凭证运行许多客户代码的服务,必须让每个客户各用一个自己的会话(谁可以共享会话)。
docker provider 还可以应用随附的 seccomp 配置文件,即一份它允许容器发出的系统调用(发给内核的请求,例如打开一个文件)清单(docs/seccomp.md)。
更多真实运行
| Page | What it shows |
|---|---|
| Simulation replay pages, all eight simulators ↗ (https://plimsollmark.github.io/plimsoll/examples/envs/index.html) | 为控制器运行而构建在仿真镜像中的八个模拟器:淋浴、buck c |
相似文章
Show HN:Pi pod – 在你自己的服务器上以沙盒方式运行 pi 编码代理
Show HN:Pi pod 是一个开源工具,可在你自己的服务器上以隔离沙盒方式运行 pi 编码代理,旨在提供一个完整的自托管智能体工程环境,包括沙盒隔离、基于 RBAC 的会话共享以及自动化能力。
Perplexity 开源了其代理沙箱(4 分钟阅读)
Perplexity 开源了其代理沙箱平台 SPACE,该平台是 Computer 背后的安全基础设施,为开发者提供隔离执行、状态恢复和网络/凭证控制等生产级代理运行时基础。
Show HN: Halo – 开源、防篡改的AI代理运行时证据
Halo 是一款开源工具,为 AI 代理提供防篡改的运行时记录,生成可审计的代理操作日志,并可由第三方验证。
Show HN: Clawk – 为编程代理提供一次性Linux虚拟机,而非你的笔记本电脑
Clawk是一个开源工具,为Claude Code等编程代理提供一次性Linux虚拟机,使其能自由运行而不危及宿主机。它通过网络白名单和文件隔离来保护密钥和系统文件安全。
@AravSrinivas:虽然沙箱已是标准化商品,但要在生产级流量下提供数百万个沙箱,仍需处理大量细节才能扩…
Perplexity 开源了其沙箱平台 SPACE(智能体代码执行沙箱平台),该平台基于 Firecracker 微虚拟机,为 Perplexity Computer 上运行的所有智能体提供隔离的代码执行环境,支持通过 HTTP API 管理沙箱生命周期。