沙箱与隔离对照:Landlock、Seatbelt 与容器,三条路各自守住了什么

2026-08-17

先把问题问死一点:模型让 agent 跑 npm install,这条命令在你的机器上到底被什么东西挡着?挡不住的时候,你是会看到一个明确的错误,还是它悄悄地就以完整权限跑过去了?

这四个项目给出的答案差得相当远,而且差的地方不在「用了什么内核特性」这一层——Landlock、Seatbelt、bubblewrap 大家都在用。差的是边界画在哪一圈,以及边界不可用时默认怎么办。下面只对照四方公开材料里白纸黑字写了的东西,查不到的维度直接说不比。

需要先声明一句:DeepSeek Harness 的 README 自述项目处于开发者预览阶段,并且用大写明确写了会有破坏兼容性的变更。下面提到的模块名、默认值、退出码都是仓库快照里的当下写法,随时可能变。

一、DeepSeek Harness:把「文件效果」抽成一个 seam,能不能拦是被上报的事实

packages/sandbox/ 下面数,是四个子包:sandbox(抽象接缝)、sandbox-local(平台实现)、sandbox-policy(策略解析)、sandbox-windows-acl(Windows 受限令牌后端)。

docs/subsystems/sandbox.md 开头就把范围钉死了:SandboxMode 只有三个值 read-onlyworkspace-writedanger-full-access,而且原文写明这套词汇只管文件效果,「网络与进程可见性不在这套词汇里」。packages/shell/bash-sandbox/README.md 在已知限制里又重申了一遍:网络访问与进程可见性刻意不做限制,所以这几个模式不是通用安全沙箱。

沿着调用路径走一遍。工具层调 ctx.sandboxPolicy.resolve() 拿到一份 per-call 的策略——优先级是「已批准的显式 mode > 会话日志里最后一条 sandbox/mode 事件 > 部署默认值」,workspaceRoot 取调用会话不可变的 cwd,无会话的调用才回落到配置里的根。然后把即将 spawn 的那串 argv 原样交给 ctx.sandbox.confine(argv, policy),拿回一串替换用的 argv 自己去 spawn。

替换成什么,看 packages/sandbox/sandbox-local/src/profiles.ts,三种方言一目了然:

  • bwrap:固定四段 --ro-bind / /--dev /dev--proc /proc--die-with-parent;只有 workspace-write 才追加 --tmpfs /tmp--bind <workspaceRoot> <workspaceRoot>
  • Landlock:把 / 作为只读根,可写清单在 read-only只有 /dev/null 一项workspace-write 才加上 /tmp 和 workspace 根
  • Seatbelt:拼一段 SBPL,(version 1) (allow default) (deny file-write*),再单独放行 /dev/null 这个字面量,之后按可写根追加 (subpath ...)

三种方言的可写根不是各写各的。packages/sandbox/sandbox/src/roots.ts 里的 writableRoots() 是唯一出处:read-only 返回空数组,workspace-write 返回 workspace 根加 /tmpos.tmpdir(),全部走 realpathSync.native 规范化再去重。文件头注释自述这么做是为了让 Seatbelt 授权和进程内文件栅栏不会漂移,避免出现「write 工具写不了 /tmp 但 bash 能写」这种不对称。

runner 怎么选,在 packages/sandbox/sandbox-local/src/index.tsPLATFORM_CHAINS 是一张三行的表:linux 是 ['bwrap', 'landlock'],darwin 是 ['seatbelt'],win32 是 ['windows-acl']。注释写明选择先按平台、探测只在候选多于一个时才做仲裁——所以 macOS 和 Windows 这两条链是不探测直接选中的,探测超时 probeTimeoutMs 默认 5000 毫秒,构造函数里断言它必须是正的有限数,注释自述的理由是 0 对 Node 意味着不限时。

最值得记住的是 STATIC_ENFORCEMENT 这张表:bwrap、Landlock、Seatbelt 标 fullwindows-acl 直接标死 partial。README 给了理由:受限令牌为了让进程能初始化必须在限制列表里保留 Everyone,因此外部对象只要给 Everyone 写权限就仍然可写;NTFS 硬链接还能让工作区内外的两条路径指向同一个文件对象。

还有一处容易被忽略:每次 confine 返回的 ConfinedArgv 上挂着这个后端自己的错误方言DENIAL_SIGNATURES 里 bwrap 是 read-only file system,Landlock 是 permission denied,Seatbelt 是 operation not permitted,Windows 那条有三个变体。注释直说:跨后端取并集是错的,因为并集会宣称某个后端根本不会产生的拒绝。另一组 RUNNER_FAILURE_RULES 用来区分「沙箱正常工作、命令被拦了」和「沙箱自己就没起来、命令压根没跑」——Landlock 这条要求退出码 125(常量 LAUNCHER_FAILURE_EXIT,定义在 native/landlock-run/packages/entry/src/index.ts并且有一行 landlock-run: 开头的致命行,同时先把 landlock-run: partial enforcement (older Landlock ABI) 这一行按整行相等剔掉,免得一条善意提示被当成失败证据;Windows 那条要求退出码 127。

拦不住怎么办?seam 的契约写死了:没有可用后端就抛 SANDBOX_UNAVAILABLE静默地以未受限方式放行对一个受限策略永远不合法

二、Claude Code:只包 Bash 一层,但文件之外还管网络

Claude Code 官方文档《Configure the sandboxed Bash tool》写明,这个沙箱包的是 Bash 命令及其子进程,默认写权限是当前工作目录加会话临时目录,读权限是整台机器除去被拒目录——文档在这里主动补了一句提醒:这个默认仍然允许读取 ~/.aws/credentials~/.ssh/ 这类凭据文件,要挡得配 sandbox.credentials 或者把路径加进 denyRead

OS 层面,官方文档写明 macOS 用 Seatbelt,Linux 与 WSL2 用 bubblewrap,WSL1 不支持,原生 Windows 不支持,Windows 上要在 WSL2 里跑。Linux/WSL2 侧还要装 bubblewrapsocat 两个包,另有一个可选的 seccomp filter 用来阻断 Unix domain socket,文档写明它由 @anthropic-ai/sandbox-runtime 提供。

和 DeepSeek Harness 明确不管网络相反,Claude Code 的网络隔离是这套沙箱的第二层:默认不预置任何域名,第一次要访问新域名时走审批。文档自己在「安全限制」一节里写明了这层的边界——内置代理默认不终止也不检查 TLS,判断依据是客户端给的主机名,所以沙箱内的代码可能用 domain fronting 一类手法绕到白名单外;实验性的 network.tlsTerminate 设置能让代理自己终止 TLS,但文档明说它不增加内容过滤。同一节还写了 allowUnixSockets 放行 /var/run/docker.sock 等于把宿主交出去,以及 enableWeakerNestedSandbox 会「相当程度地削弱安全性」。

最该拿去和 DeepSeek Harness 对着看的是这一条:官方文档写明,默认情况下如果沙箱因为依赖缺失或平台不支持而起不来,Claude Code 会显示一条警告,然后不带沙箱地执行命令;要让它变成硬失败,得把 sandbox.failIfUnavailable 设成 true。两边都把这件事写在明面上,但默认方向正好相反:一边默认 fail closed 抛错,一边默认告警后继续。

逃生口两边也都写了。Claude Code 有 dangerouslyDisableSandbox 参数,命令因沙箱受限失败时模型可能带这个参数重试,重试的那次走常规权限流程;把 allowUnsandboxedCommands 设成 false 就是文档里说的 Strict sandbox mode,这个参数被完全忽略。DeepSeek Harness 那侧在 packages/sandbox/sandbox/src/escalation.ts 里:WIDER_MODES 规定只能严格往宽了升(read-only 能升到 workspace-writedanger-full-accessworkspace-write 只能升到后者),sandbox_permissionsjustification 两个参数必须成对出现且理由不能是空串,否则直接抛错。Codex 与 Pi 这一维,我们在本次核对的材料里没有找到对应的公开说明,不比。

还有一处 Claude Code 文档专门列了而另外三方我们没查到对应表述的:受保护路径。沙箱在可写目录内部仍然拒绝写 .claude 下的设置、skills/agents/commands/hooks 目录、.mcp.json,以及 .git 里的 hooksconfig;文档明说 allowWrite 也豁免不了这些路径,唯一关掉的办法是整层关闭文件隔离。理由文档自己写了:能改这些文件的命令可以给自己发权限,或者塞一个在沙箱外运行的 hook。

范围上,官方文档也说得很直:内置的 Read/Edit/Write 走权限系统而不走沙箱;computer use 跑在你真实的桌面上;subagent 与父会话同进程、共用同一份沙箱配置。想把 MCP server 和 hooks 也圈进去,得换成《Choose a sandbox environment》那页里的另一种方案。

三、Codex:把 bubblewrap 编进自己的二进制,网络交给 seccomp

Codex 仓库里的 docs/sandbox.md 只有一句话,指向线上的安全文档,所以想看机制只能读 codex-rs/

第一处反直觉的地方在 codex-rs/bwrap/:这个 crate 产出一个名叫 bwrap 的可执行文件,build.rs 会从 codex-rs/vendor/bubblewrap 编译 bubblewrap.cbind-mount.cnetwork.cutils.c,通过 pkg-config 链 libcap,main.rs 直接调进 C 侧的 bwrap_main。也就是说 bubblewrap 的源码被 vendor 进了仓库并编进自家二进制。codex-rs/core/README.md 补齐了取舍:Linux 侧优先用 PATH 上(当前工作目录之外的)第一个 bwrap,缺失时回落到随 Codex 一起发的 codex-resources/bwrap,并通过通知通道抛一条启动告警;bwrap 太老不支持 --argv0 时走一条兼容路径。同一份 README 还写了 WSL2 走正常 Linux 路径、WSL1 因为造不出所需的 user namespace 不被支持。

DeepSeek Harness 这侧是另一种做法:sandbox-local 探测的是系统上的 bwrap,探测方式是拿真实 profile 加 -- true 跑一次看退出码,探不通就往链条下一级走 Landlock,都不行就 SANDBOX_UNAVAILABLE。一个自带、一个探测,落差会在离线或最小化镜像里显出来。

第二处是 Landlock 的定位。codex-rs/linux-sandbox/src/landlock.rs 的模块注释写得很清楚:进程内的 Linux 沙箱原语这里只负责 no_new_privs 和 seccomp,文件系统限制由 bubblewrap 负责,Landlock 辅助函数「作为遗留/后备工具保留」,那个装 Landlock 规则的函数上面还挂着「目前未被使用」的注释。而在 DeepSeek Harness 里,Landlock 是 Linux 链条上 bwrap 之后的第二级正式候选,还带自己的启动器和退出码契约。同一个内核特性,一个当后备,一个当第二梯队,说完就停。

第三处是网络。Codex 的网络限制走 seccomp 过滤器,装在当前线程上因此只有子进程继承:受限模式下 connectacceptaccept4bindlistensendtosetsockopt 等一串系统调用被无条件拒绝,socketsocketpair 只放行 AF_UNIX,源码里还留了一条注释说明为什么故意不拦 recvfrom(拦了 cargo clippy 这类工具的 socketpair 会挂)。同时无论哪种网络模式,ptraceprocess_vm_readvprocess_vm_writev 和三个 io_uring_* 都被拒。bwrap 参数那侧也能看到 --unshare-user--unshare-pid,网络受限时再加 --unshare-net

对照就很直接了:Codex 用 seccomp 加 namespace 把网络和进程可见性一起圈进来,Claude Code 用外部代理按域名管网络、并明说默认不看 TLS,DeepSeek Harness 则明确宣布网络与进程可见性不在自己的词汇表里。三种不同的边界,不排名——但如果你的诉求是「别让它把代码传出去」,DeepSeek Harness 的三个模式无论怎么调都不覆盖这一条,这是它自己写在 README 已知限制里的。

顺带一个小细节:codex-rs/sandboxing/src/seatbelt.rssandbox-exec 的路径硬编码成 /usr/bin/sandbox-exec,注释自述是为了防止攻击者往 PATH 上注入同名程序。DeepSeek Harness 的 seatbeltExec() 默认返回的是不带路径的 'sandbox-exec'。两处写法不同,摆在这里,不延伸。

四、Pi:不做沙箱,并且把「为什么不做」写进了文档

packages/coding-agent/docs/security.md 有一节标题就叫 No Built-in Sandbox:Pi 不包含内置沙箱,内置工具以 pi 进程的权限读写文件、执行 shell,扩展是 TypeScript 模块、同样权限。文档自述这是有意为之,理由原文的意思是——一个半吊子的进程内沙箱很容易被误当成安全边界,而它实际上仍然依赖宿主的 shell、文件系统、包管理器、凭据和扩展代码;真正的隔离得来自操作系统或者虚拟化/容器边界。同一份文档还特意澄清 project trust 只是输入加载的守卫,不是沙箱。

于是隔离的活全在 packages/coding-agent/docs/containerization.md 里,两种大模式:整个 pi 进程跑进隔离环境,或者 pi 跑在宿主上、把工具执行路由进隔离环境。文档给的表格列了三种:Gondolin 扩展(本地 Linux 微虚拟机,把宿主 cwd 挂到 VM 里的 /workspace,覆盖 readwriteeditbashgrepfindls 七个内置工具和用户的 ! 命令,要求 Node.js >= 23.6.0 和 QEMU)、纯 Docker(整进程进容器,文档直说 provider 的 API key 会进容器)、OpenShell(策略受控沙箱,每个沙箱都需要一个活跃的 gateway)。文档还提醒:远端 gateway 下项目文件不会从宿主 bind-mount,沙箱里的写不会反映到你机器上;反过来,读写方式 bind-mount 宿主工作区时,容器或 VM 里的写仍然会改宿主文件。

这条路和另外三家不在一个层面上比较——它把边界整个挪到了进程之外。Claude Code 的《Choose a sandbox environment》里有一个同向的判断:内置 Bash 沙箱只约束 Bash,文件工具、MCP server 和 hooks 仍跑在宿主上,要连这些一起圈住就得上 dev container、容器或虚拟机。两家文档对「进程内沙箱覆盖不到什么」的认识一致,区别只是一家提供了进程内那一层,一家不提供。

五、这些差异什么时候会真的咬到你

  • 你在原生 Windows 上开发。 Claude Code 官方文档写明原生 Windows 不支持,得进 WSL2。DeepSeek Harness 有 win32 这一级,但 STATIC_ENFORCEMENT 里它被写死成 partial,README 把 Everyone 保留和 NTFS 硬链接两个缺口列了出来。Codex 的 codex-rs/core/README.md 描述了非提权受限令牌后端和提权沙箱两条 Windows 路径。三家写法各不相同,但没有一家在 Windows 上宣称完整边界。
  • 你要跑无人值守的批处理。 先去确认「沙箱起不来时默认怎么办」这一条:Claude Code 默认是告警后不沙箱执行,要 sandbox.failIfUnavailable 才硬失败;DeepSeek Harness 的 seam 契约是抛 SANDBOX_UNAVAILABLE。这是配置动作,不是选型动作,先看清默认值。
  • 你怕的是数据外泄而不是文件被改。 DeepSeek Harness 三个模式都不覆盖网络,这是它自己声明的范围。Claude Code 有域名层,但文档同时写了默认不检查 TLS 的后果。Codex 走 seccomp。按你真正担心的那件事去挑,别拿「有沙箱」当结论。
  • 你要把 MCP server、hooks、文件工具也圈进去。 进程内那一层做不到——Claude Code 文档和 Pi 文档在这点上说的是同一件事,得整进程进容器或 VM。

最后拎一句出来:partial、「默认不检查 TLS」、「会相当程度地削弱安全性」这些词,都是四方在自己的限制章节里写的,不是我们的评价。有沙箱不等于安全,这是四份材料的共同底色。


本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

文中涉及的 Claude Code 内容依据其官方文档(code.claude.com/docs)整理,该产品闭源,本文不推断其实现; Codex 依据 github.com/openai/codex 快照 c6058cc、Pi 依据 github.com/earendil-works/pi 快照 027a5847 整理。 本文只对照各方公开写明的机制,不对三者做优劣排名。

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。