Hacker News AI · 2026/10/12 07:47:59

Plimsoll 开源自托管沙箱:引入隔离等级地板机制强制校验 AI 代码执行安全

原标题:Show HN: Plimsoll, a self-hosted sandbox for code your AI agent writes
68AI 研判分
核心综述

Plimsoll 发布了一款专为 AI 代理生成代码设计的自托管沙箱,其核心创新在于将“隔离强度”作为可验证的元数据而非模糊承诺。系统定义了进程、容器、内核、虚拟机四个隔离层级,允许请求方设定最低安全阈值(Floor),若实际运行环境低于该阈值则直接拒绝执行。此外,它内置了针对不可信代码的自动化测试运行器,并通过 SHA-256 哈希确保请求与结果记录的完整性,解决了 AI 代码执行中“黑盒安全”的信任难题。

报道全文原始报道全文

本文目录2 个章节

plimsoll runs untrusted code, such as code an AI agent wrote, inside a sandbox, and every result says how strong that sandbox was. A request can demand a minimum strength; if the sandbox is weaker, nothing runs.

The name comes from the <dfn>Plimsoll line</dfn>, the load limit painted on the outside of a ship's hull where anyone can check it. plimsoll does the same for the <dfn>isolation tier</dfn>: how strong the wall around a run is, stated as one of four levels, weakest first: process, container, kernel, vm. The tier is a value on every result, not a sentence in a datasheet. A request can set a <dfn>floor</dfn>, the weakest tier it will accept, and the official Go client checks the tier again when the result comes back.

The same machinery lets plimsoll run an exam on code an agent wrote, not only contain it. A trial runner built into the simulation image runs the agent's program against scenarios that program cannot read: each one reaches the runner in a file deleted before the program starts. Every run returns SHA-256 hashes of the request as sent and the result as returned, which the official clients recompute (how). A separate program outside the daemon, plimsoll-attest, signs the checked results, refuses a chain of calls with one missing, and replays a run. The exam itself (the simulated system, the scenarios, the pass mark) stays yours: plimsoll supplies the boundary and the evidence.

See one real run

An AI agent wrote a <dfn>controller</dfn>: a program that reads a system's state at every <dfn>tick</dfn> (here, one 10 ms step of the simulation) and decides how to push it. plimsoll ran it against a simulated <dfn>cart-pole</dfn>, a cart on a rail with a pole hinged on top, which the controller keeps upright by pushing the cart left and right. The simulator is compiled to <dfn>WebAssembly</dfn>, a portable bytecode that runs inside a host program, and built into the sandbox image. The controller was the only file the caller sent, through the ordinary project API, and it ran at the container tier.

The physics oracle: a cart with a pole balanced on it by an agent-written controller, beside the SHA-256 fingerprint of each run's trajectory

The cart-pole is the standard teaching problem in control engineering. The runner, a program built into the image that runs the controller against the simulator, records the run's <dfn>trajectory</dfn>, every state at every tick, and hashes it into a <dfn>fingerprint</dfn>: a SHA-256 hash, so equal fingerprints mean identical numbers. Run the accepted controller twice and the two fingerprints match. The agent's first draft had two gains (multipliers in its formula) with the wrong sign: it drops the pole at 5.96 seconds, and its fingerprint differs.

Pole angle and cart position over twenty seconds, for the accepted controller and for the draft that fell

Open the live run report ↗ to replay both runs in the browser, read the controller the agent wrote, and see what the page deliberately does not claim. One execution of the example makes three sandbox runs: the accepted controller twice and the draft once. Reproduce them with make docker-images && go run ./examples/oracle.

The two things it does

It reports the wall. Every result states the isolation tier the run actually got. Every request can set a floor, and a request whose floor the daemon cannot meet is refused before any code runs: ErrInsufficientIsolation means nothing ran.

GO
res, err := provider.Sandbox.RunJavaScript(ctx, sandbox.Request{
    Code:             "console.log(6 * 7)",
    MinimumIsolation: sandbox.IsolationVM, // ErrInsufficientIsolation means nothing ran
})
// res.Isolation reports the boundary that actually ran.

Every refusal that ran nothing is marked as one, with a reason (request, permission, protocol, unsupported, isolation, environment, capacity). sandbox.NotDispatchedReason(err) reads the mark, whether the <dfn>provider</dfn> (the backend that runs the code) is in your own process or behind the daemon. An error without the mark may have come after the code started, so it is never safe to retry automatically. A mark counts only on an answer that carries the client's request ID back, so a proxy that serves one request's answer for another cannot make a call that ran read "nothing ran" (what comes back, and what it means).

The tier is evidence the daemon collected: its configuration and its own startup checks. It is not <dfn>attestation</dfn>, cryptographic proof from the hardware of what software is running, and no provider here offers that. docs/isolation-tiers.md lists what each tier rests on.

It lets agent-written code call your API without ever holding your credential, your API's address, or open network access. A <dfn>grant</dfn> gives one run permission to call listed routes of one API. The code calls through a small client plimsoll puts in the sandbox, and the <dfn>broker</dfn>, the part of plimsoll outside the sandbox that makes the real call, checks the caller and the exact route, then attaches the credential itself. The credential is <dfn>minted</dfn> per run: plimsoll asks for it once per run, so it can be a fresh short-lived token, and it never enters the sandbox. Code that skips the client and calls out by hand gets no further, because the broker, not the client, enforces the rules. See docs/capability-grants.md.

(注:更多技术实现细节与完整 API 文档请查阅原项目 README)