从0打造编码Agent 导读 3 课 + 2 条岔路 + 1 节实验课 ≈ 2.5 小时
A4 沙箱与隔离:规则拦不住一个自己写文件的 Python 脚本,谁拦? 章导读

沙箱与隔离:规则拦不住一个自己写文件的 Python 脚本,谁拦?

上一章的权限规则和模型审批,看的都是「命令长什么样」。一个 python -c 脚本在运行时才决定写哪个文件,它们看不见。这一章交给操作系统:不管命令怎么写,越界的系统调用都会被内核拒绝。我们在本机用 sandbox-exec 加载 Codex 的真策略,把 14 条命令逐条真跑,看沙箱拦在哪、拦了什么、又拦不住什么;再看五家在沙箱、容器、不做之间怎么选。

主线定位

累计数据流 · 一次任务在 harness 里经过哪些环节 已学本课新增还没学
每一轮循环都经过
🧭

两层为什么互补:规则在执行前、按模型提出的调用裁决;沙箱在运行中、按进程实际的行为裁决。规则能读懂「你想干什么」但拦不住改写过的写法;沙箱不管你怎么写,只认真实的系统调用,但它不知道你「本来想干什么」。缺一层都不够。

本章问题链

每一课只回答一个问题;上一课的失败,逼出下一课的问题。

课这一课的问题朴素方案在哪个真数字上失败一句话
第 1 课规则说不许写 ~/.ssh,一个 python -c 脚本写了,谁拦?不套沙箱时,权限层按前缀放行(规则 #8),python -c 把内容真写进了伪 HOME(T9 r07 副作用发生);同一条命令在 Codex 沙箱下被拦规则管模型提出的调用,沙箱管进程实际做的事
第 2 课沙箱到底拦在哪、拦了什么?只放行工作区的朴素策略下,写工作区里的 .git/config 照样成功(T9 r03),能借此改 remote、植入 hook;Codex 策略把它单独保护好沙箱是「默认全拒 + 一份被 bug 磨出来的白名单」,连工作区里的 .git 也要单独保护
第 3 课沙箱、容器、干脆不做——各家怎么选,代价是什么?「有容器就安全」:Hermes docker 后端没有 seccomp、默认容器内 root、默认联网,用户参数还能覆盖硬化;选容器后审批门整层跳过容器、沙箱、不做都是真实选项,关键是写清隔离责任在谁、边界在哪
岔路两道闸都开着,数据还能从哪漏出去?—断了写权限不等于断了泄露:私有数据 + 不可信内容 + 外发能力三者同在就危险
岔路Pi 为什么宁可不做沙箱?—半个进程内沙箱会被误认为安全边界;要边界就上容器
实验课14 条命令 × 4 策略的真实录制回放—沙箱挡写和联网、挡不住读;拒绝判定只是关键词 + 退出码启发式,会误报会漏报

演进地图

隔离设计的演进:规则拦不住的,交给一层比一层硬的边界
  1. 上一章 只有进程内规则
    A3 权限规则、Pi 的确认弹窗
    解决了在执行前按「工具名 + 命令文字」裁决,拦住模型明着提出的危险调用。
    卡在规则看不见子进程实际做什么:不套沙箱时,python -m unittest …; python -c "写 ~/.ssh" 里权限层按前缀放行(rule_index 8),复合命令里的 python -c 把内容真的写进了伪 HOME(T9 r07)。
  2. allow default 再挖掉写与网络
    解决了约束进程实际发出的系统调用(含全部子进程):写工作区外、联网被内核拒。14 条命令里拦下 6 条。
    卡在工作区里面不设防:同一条写 .git/config 的命令,朴素策略下照样成功(T9 r03)——能借此改 remote、植入 git hook。
  3. Codex Seatbelt(seatbelt_base_policy.sbpl,122 行)
    解决了从 (deny default) 起步,可写根用参数注入,.git / .codex / .agents 用锚定正则单独保护,默认断网。workspace-write 下拦到 9 / 14 条。
    卡在沙箱挡写和联网,不挡读:读伪 HOME 里的假私钥,在四种策略下全部成功(T9 r12)——读到的东西还能经合法命令传出去。
  4. Claude Code(OS 沙箱 + 审批)、claude.ai(gVisor 容器)、Cowork(完整 VM)
    解决了把边界移出进程,按「爆炸半径」分档;规则管「允许调用什么」,隔离管「调用之后能碰到什么」。
    卡在私有数据 + 不可信内容 + 外发能力三者同在时,数据可经已放行的域名外传——规则与沙箱都放行(lethal trifecta,→ 岔路 B1)。
  5. 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 生成。

贯穿任务:给 test_parse_date 的 trace 加一列「沙箱列」

全课的贯穿任务是让失败的 test_parse_date 通过。这一章给它的 trace 加上沙箱列:每条 bash 命令在系统调用层是否被拦下、退出码与 stderr、以及 Codex 的拒绝判定。

三层交互

层本章里的组件在哪一课
概念动画两道闸流水线(权限规则 → OS 沙箱 → 执行),A3 的绕过样例逐个过闸,先预测哪道闸拦下再揭示第 1 课
算法模拟器(真算法、小数据)Codex is_likely_sandbox_denied 的 JS 移植,对每条录制现场判定「是否被沙箱拒绝」,与真值比出真/假阳阴第 2 课、实验课
真实数据本机 sandbox-exec 加载 Codex 真策略录下的 14×4 结果:切换策略看边界怎么变、每条命令的真实 stderr 与退出码第 2 课、实验课

沙箱拒绝判定在浏览器里现算,不联网:is_likely_sandbox_denied 从 pin 副本(8cabf5a 的 sandboxing/src/denial.rs:13-69,Apache-2.0)逐行移植成 JS;期望值取自项目 miniagent/denial.py,并用本机 rustc 编译的逐行 Rust 转写做第三方对照——三者在全部 64 条用例上判定一致(见实验课与交付报告)。

项目推进:给 miniagent 的 bash 工具外包一层 sandbox-exec

对应 04-实践项目/MILESTONES.md 的「A4 · 沙箱与隔离」。实现已经在 miniagent/sandbox.py 与 denial.py 里:bash 工具的命令先清洗环境变量(只留最小 PATH/HOME/LANG),再用 sandbox-exec 套一层策略;被拒的结果附上 is_likely_sandbox_denied 的判定回灌模型。代码 AI 来写,决策你来做。

🧭
你要拍板的决策
  1. 沙箱放哪一层?单个工具(只包 bash)还是整个进程?项目选了只包 bash,代价是 read_file 这类工具不受 OS 限制——先想清楚这个口子能不能接受。
  2. 用什么后端?sandbox-exec(仅 macOS,且 Apple 已标 deprecated)、Docker、或像 Pi 那样不做但写出诚实的论证。项目用 sandbox-exec,并在 DECISIONS.md 记下长期可用性风险。
  3. 默认联网吗?项目:不联网。想联网就得像 Codex 那样上代理 + 把连接归因到具体工具调用(第 3 课、岔路 B1)。
  4. 限制读吗?两种 Codex 策略都不限制读——T9 的 r12 在四种策略下都读出了伪 HOME 里的假私钥。沙箱挡住了写和联网,挡不住读后回传给模型。

验收标准(全部可测,对应设计卡 M4 ①-⑥):

坑与带走

症状后果怎么办
以为 deny 规则能拦住子进程python -c 照写不误上 OS 级沙箱,约束进程实际发出的系统调用
只把工作区设为可写.git/config 仍可改,能植入 hook、改 remote受保护元数据(.git/.codex/.agents)单独 deny
沙箱里默认联网数据经合法命令外传默认断网;要联网就上代理 + 连接归因
以为断了写权限就不会泄露沙箱不挡读,读到的东西还能外传明白沙箱挡写和网络、挡不住读(岔路 B1)
把「有容器」当「安全」可能默认 root、默认联网、无 seccomp、参数可覆盖逐项核对硬化参数,别看营销页
把拒绝判定当精确结论误报(输出里有 sandbox 一词)与漏报(网络被拒无关键词)只作提示,保留原始 stderr 给模型
依赖 sandbox-execApple 已标 deprecated,长期可用性存疑在 DECISIONS.md 记录风险与替代方案

带走这些句子(各课「一句话」):

  1. 规则管的是模型提出的调用,沙箱管的是进程实际做的事;python -c 的删除发生在子进程里,只有沙箱看得见。
  2. 好沙箱是「默认全拒 + 一份被真实 bug 一条条磨出来的白名单」,连工作区里的 .git 都要单独保护;它挡写和网络,挡不住读。
  3. 沙箱、容器、干脆不做,都是真实的设计选项;关键不是「有没有」,而是写清楚隔离责任在谁、边界画在哪、默认开不开。

深入这一章

按你愿意花的时间选。每条都实际打开核对过(2026-09-26);外部链接都需要联网,离线包里只保留链接。

🎮 想更直观 10–30 分钟

先玩本章的组件;下面三条是外部最好的图解入口
15 min英文需联网 2025-10-20
文件系统隔离与网络隔离两张架构图:网络流量经 Unix socket 走外部代理并执行域名规则;并给出「沙箱让权限弹窗减少 84%」。
对应第 1、2 课。本章的边界图就是把这两张图落到一组真实录制上;先看它建立全貌。
25 min英文需联网 2026-05-25
三档 containment 的爆炸半径:claude.ai 用 gVisor 临时容器 / Claude Code 用 OS 沙箱加人工审批 / Cowork 用完整 VM;还有「经已放行域名外传」的真实教训。
对应第 3 课与岔路 B1。本章 L3 的三档同心圆直接出自这里。
约 5000 字中文需联网 2026-05-22 · 第三方投稿
容器共享内核、VM 重、microVM 居中,讲的是云端沙箱的取舍。
对应第 3 课。中文入口,补隔离机制的直觉;注意它讲的是云端沙箱,本机 Seatbelt / bwrap 以官方文档与 pin 源码为准。

🔬 想吃透原理 1–3 小时

唯一必看:Anthropic《Beyond permission prompts》。其余按课挑
25 min英文需联网 2025-10-20
为什么要沙箱(弹窗太多、审批疲劳)、文件系统与网络两层边界怎么搭、代理如何执行域名规则,以及为什么它仍然「不是完整隔离边界」。
对应第 1、2、3 课,是全章的主心骨。把「规则拦不住的交给沙箱」这件事从动机讲到实现。
25 min英文需联网 2026-05-25
三档 containment、爆炸半径、审批疲劳遥测(用户批准约 93% 的弹窗),以及经已放行域名外传的真实教训。
对应第 3 课与岔路 B1。把「沙箱」放回整个防护体系里看,读完就懂为什么还要防数据外传。
20 min英文需联网 2025-09-30 · 第三方
YOLO 模式的三类风险、如何挑选循环里的工具、签发最小范围的凭据。
对应第 3 课与岔路 B1、B2。把「不做沙箱就上容器」和「最小凭据」讲得最实。

📄 想读原文 按需

官方文档与 pin 源码;全部需联网,官方文档的本地快照由 P6 统一处理
官方文档需联网
沙箱作用于 Bash / PowerShell / Monitor 命令及其子进程,macOS Seatbelt、Linux/WSL2 bubblewrap,原生 Windows 不支持;仍自认「not a complete isolation boundary」;auto mode + 沙箱下可按命令声明 allowed_domains。
官方文档需联网
沙箱模式(技术边界)与审批策略(何时停下询问)是两个独立控制;文档已从 developers.openai.com 迁到 learn.chatgpt.com。
源码需联网
第 2 课:122 行的基础策略,(deny default) 起步,连 python multiprocessing 的 ipc-posix-sem、PyTorch 的共享内存都要单独放行——白名单是被真实 bug 磨出来的。
源码需联网
岔路 B2:「A partial in-process sandbox would be easy to misunderstand as a security boundary … Real isolation needs to come from the operating system or a virtualization/container boundary」。
源码需联网
第 3 课:「The only security boundary against an adversarial LLM is the operating system」;默认 local 后端直接在宿主机执行。
🚫

我替你排除了什么:「OpenCode 官方建议用 Docker 作沙箱」这类二手说法(未核实——官方文档只把 Docker 列为运行方式,TUI 里有一条容器运行 tip,都不是安全建议)· Hermes 营销页「五后端 + 容器硬化 + namespace 隔离」(deep-research 0-3 否决;源码直证是 7 种 backend、默认 local 不隔离、docker 部分硬化且可被参数覆盖)· 基于逆向的沙箱细节(cc-notebook 一类,未经官方核实)· 专讲沙箱实现机制的视频:本档暂缺——检索到的多为 VM/容器跑 YOLO 的泛讲(MIT L7 只到这一层),没有讲清 Seatbelt / bwrap 具体机制且经过验证的,故不收。