沙箱、容器、干脆不做——各家怎么选,代价是什么?
本课一问:Codex 把沙箱做到 122 行白名单,可别家未必这么做。沙箱、容器、干脆不做——五家怎么选,各自的代价是什么?
这一课先不碰:两道闸都开着数据还能从哪漏(岔路 B1);Pi 不做沙箱的完整论证(岔路 B2);具体怎么给自己的项目配沙箱(项目推进在章首页)。
从已有出发
前两课都在讲 Codex 的 OS 沙箱。但「用什么隔离」是一道设计选择题,不是只有一个答案。往回退一步问:一个 harness 可以把隔离责任放在哪?
- 放进 harness 内置的 OS 沙箱(Claude Code、Codex);
- 完全不做,交给用户自己套容器(OpenCode、Pi);
- 提供容器后端,但默认不开、可被覆盖(Hermes)。
先自己设计:「有容器就安全」
一个很自然的判断:把 agent 塞进 Docker,不就隔离了吗?有容器 = 安全。
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
「容器」这个词覆盖的范围很宽:从「只是换个文件系统视图」到「dropped 权限 + seccomp + 只读根 + 断网」。把两者都叫「容器」,就会把前者的安全感套到后者的场景上。
不看「有没有容器」,看四件具体的事:① 硬化参数逐项是什么(cap、seccomp、user、network);② 默认开不开;③ 参数能不能被用户覆盖;④ 选了容器之后,进程内的审批/规则还生效吗。下面把五家并排,每一栏都点得开源码出处。
cap-drop ALL 加三项 cap-add、no-new-privileges、两个 tmpfs,没有 seccomp;默认容器内 root(docker_run_as_host_user 默认 False)、默认联网(docker_network 默认 True);用户的 docker_extra_args 追加在最后,可覆盖上述硬化;而且一旦选了容器后端,pin 上审批门整层跳过——两层防护变成互斥,不是叠加。 改进:五家并排,点开看出处
三档出自 Anthropic《How we contain Claude across products》(2026-05-25,官方一手,需联网);圈内「是否与宿主共享」为文章表述。
试三件事:① 点 OpenCode:它的「机制」一栏——静态源码里没有 OS 沙箱(grep seatbelt/bwrap/landlock 零命中),用 cross-spawn 裸 spawn;② 点 Hermes 的「覆盖面」和「责任」两行,看硬化清单和「选容器后审批门跳过」;③ 点最下面 containment 三档同心圆的每一圈,看它与宿主共享什么。
Hermes 的硬化,逐行核对
先看它给每个容器加的安全参数——这是它的全部硬化:
再看关键的一条:一旦选了隔离容器后端,危险命令的审批检查整层跳过:
这不是说 Hermes 不安全,也不是说它「没有容器隔离」。它有 7 种 terminal backend、docker 后端确有部分硬化;它的立场写在 SECURITY.md 里,而且很诚实:
唯一的安全边界,是操作系统。
—— 进程内的任何筛查(审批门、输出脱敏、模式扫描、工具白名单)都只是在一个被攻击者影响的字符串上做启发式,它自己就这么说。 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条源码直证:config_defaults.py 默认 local;docker.py 硬化参数;approval.py:3402 容器后端跳过审批。默认值与硬化清单均在 pin 副本上核对。 出处Hermes SECURITY.md(pin 56dc01d9) ↗需联网
演进地图
- 上一章 只有进程内规则A3 权限规则、Pi 的确认弹窗解决了在执行前按「工具名 + 命令文字」裁决,拦住模型明着提出的危险调用。卡在规则看不见子进程实际做什么:不套沙箱时,
python -m unittest …; python -c "写 ~/.ssh"里权限层按前缀放行(rule_index 8),复合命令里的 python -c 把内容真的写进了伪 HOME(T9 r07)。 - 第一代 朴素 OS 沙箱:只放行工作区allow default 再挖掉写与网络解决了约束进程实际发出的系统调用(含全部子进程):写工作区外、联网被内核拒。14 条命令里拦下 6 条。卡在工作区里面不设防:同一条写
.git/config的命令,朴素策略下照样成功(T9 r03)——能借此改 remote、植入 git hook。 - Codex Seatbelt(seatbelt_base_policy.sbpl,122 行)解决了从
(deny default)起步,可写根用参数注入,.git/.codex/.agents用锚定正则单独保护,默认断网。workspace-write 下拦到 9 / 14 条。卡在沙箱挡写和联网,不挡读:读伪 HOME 里的假私钥,在四种策略下全部成功(T9 r12)——读到的东西还能经合法命令传出去。 - Claude Code(OS 沙箱 + 审批)、claude.ai(gVisor 容器)、Cowork(完整 VM)解决了把边界移出进程,按「爆炸半径」分档;规则管「允许调用什么」,隔离管「调用之后能碰到什么」。卡在私有数据 + 不可信内容 + 外发能力三者同在时,数据可经已放行的域名外传——规则与沙箱都放行(lethal trifecta,→ 岔路 B1)。
- 并行选择 干脆不做进程内沙箱Pi 核心、bypassPermissions解决了不做「半个沙箱」以免被误认为边界;坦白这是 YOLO,把真正的隔离交给容器 / 微虚拟机。卡在什么都不套时,T9 的 14 条越界命令全部发生(none 策略拦下 0 条);所以 Pi 说:要边界就上容器。
数字口径:全部由本机 sandbox-exec 加载 Codex 真策略录制(T9,2026-09-26 · macOS 26.6.2 · 伪 HOME,不碰真实 ~/.ssh);a04-sandbox-fixtures.json,2026-09-26 生成。
反过来说也不对。它有 7 种 backend、docker 后端做了 cap-drop ALL 等部分硬化——只是默认 local(不隔离)、硬化不含 seccomp、可被用户参数覆盖、且选容器后审批门跳过。准确的说法是「默认不开、开了也有边界但要逐项看」,不是「有」或「没有」两个字能概括的。
未核实。官方文档只把 Docker 列为一种运行方式,TUI 里有一条「在容器里跑」的 tip,都不是安全建议。可以确证的只有:静态源码里没有 OS 沙箱实现,bash 工具描述自认以宿主用户的权限运行。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 「用什么隔离」没有唯一答案;把选择讲清,才能判断一个 harness 的边界够不够 |
| ② 为什么不能只看「有没有容器」? | 「容器」覆盖的强度差别极大;真正决定安全的是硬化参数、默认值、可否覆盖、以及隔离与审批是否叠加 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条源码直证:Hermes docker.py 硬化清单无 seccomp、默认 root/联网、extra_args 可覆盖;approval.py:3402 容器后端跳过审批。均在 pin 副本核对。 出处待补 |
| ③ 各家怎么选? | Claude Code / Codex 内置 OS 沙箱;OpenCode 静态源码无 OS 沙箱、交给用户;Pi 故意不做、推荐外部隔离;Hermes 默认 local、可选容器后端 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条五家均为 pin 源码或官方文档直证;红线:不引 Hermes 营销页的「五后端+容器硬化+namespace 隔离」,也不反讲成「没有容器隔离」。 出处pin 源码 + 官方文档 ↗需联网 |
一句话
先停在这里:记住「逐项核对硬化」和「隔离与审批可能互斥」两件事。
再深一层:两条岔路——两道闸都开着,数据还能从哪漏出去? 与 Pi 为什么宁可不做沙箱?
下一个缺口
边界设好了,任务一长,上下文装不下怎么办? → A5 上下文管理
我们已经把「谁拦、拦在哪、拦不住什么」讲完了:规则管提出的调用,沙箱管实际的系统调用,容器/VM 把边界移出进程。边界这条线到此告一段落。下一章换一个维度:一个真实任务要跑很多轮,对话越堆越长,超过模型窗口怎么办?
你可能会问
- 「那我到底该用哪种?」 看你的威胁模型:改自己项目的代码、你在旁边看着,OS 沙箱 + 审批(Claude Code 档)够用;跑不可信仓库或无人值守,就往外圈走——容器甚至完整 VM(见 containment 三档)。Pi 和 OpenCode 把这个选择显式交给你,代价是你得自己搭。
- 「gVisor 和普通 Docker 差在哪?」 gVisor 是用户空间内核,拦截并重新实现系统调用,比共享宿主内核的普通容器多一层;claude.ai 用它跑一次性的临时任务(见三档同心圆的内圈)。
- 「Windows 上怎么办?」 Claude Code 原生 Windows 不支持沙箱(WSL2 里用 bubblewrap);Codex 在 Windows 上用受限令牌、MXC 沙箱在推进中。跨平台一致是这块最难的地方。