Featured image of post Codex 沙箱这版补了四个洞——你抄的那份 workspace-write 配置,可能正压在其中一条上

Codex 沙箱这版补了四个洞——你抄的那份 workspace-write 配置,可能正压在其中一条上

从 openai/codex 的 commit 反推沙箱边界变更:WSL interop 逃逸、凭证经 shell 快照回灌、Seatbelt 通配拒绝 XPC、apply_patch 扩权。分清哪些已进 0.155 稳定版、哪些还只在 alpha,附一份按平台检查自己权限配置的清单。

你按教程把 workspace-write 配好了,以为边界稳了。但去翻 openai/codex 这个月的 commit,会发现沙箱在补的不是功能,是洞:WSL 上借 wsl.exe 以 root 重进发行版、shell 快照把 broker 替换掉的假凭证还原成真的、macOS Seatbelt 里一条通配拒绝。给 macOS/WSL 上重度用 Codex 的人:旋钮之外,你得知道沙箱在防谁。

先说清楚一件事,因为这决定了你该不该动:这几个洞不在同一个发布里。其中两个已经进了 9 月 17 日的稳定版 rust-v0.155.0,另外三个还只存在于 0.156.0-alpha.*。很多人看到「alpha 修了安全问题」就想去升 alpha——方向反了。

审批是「问不问人」,沙箱是「批准之后还能碰什么」——两层的失败模式完全不同

这两层经常被当成一回事,但它们坏掉的样子差很远。

审批策略(approval_policy)决定的是:这一步要不要停下来问人。它失败的样子是你误批了一次危险操作——你走神了、你按了 y、你没细看那条命令。人看错了,但至少有人在看。

沙箱(sandbox_mode + 平台后端)决定的是:批准之后,这个进程在操作系统层面还能碰什么。它失败的样子是批准之后进程仍然越界——没人看得到,因为审批已经过了。Seatbelt、Landlock+seccomp、Restricted Token 在做的就是这个。

这个区别为什么重要?因为它解释了为什么「我审批很谨慎」不能替代读沙箱变更。你审批得再细,也审不出「workspace-write 在 WSL 上开网络时,interop 能起一个沙箱外的 Windows 进程」这种事——那不是一条命令长什么样的问题,是内核边界在哪的问题。本文接下来拆的四个洞,全部属于第二层。

一句话分界:审批是的边界,沙箱是内核的边界。人会被说服,内核不会——前提是策略写对了。

WSL 上「开网络」曾经不等于「文件边界还在」

这是四个里已经进稳定版的两个之一,也是 WSL 用户最该回头看一眼的。

workspace-write 默认不开网络。为了跑 npm installgo mod download 这类活,几乎所有人都会在配置里打开它:

1
2
[sandbox_workspace_write]
network_access = true

打开之后,你可能默认「文件边界还是 workspace-write 那套,只是多了一条网线」。PR #44286Block WSL interop escapes from restricted filesystem sandboxes)的 Why 段说的不是这样:

With network access enabled, WSL interop can launch Windows processes outside the Linux filesystem sandbox, including using wsl.exe to reenter the distribution as root.

翻译成工程语言:网络一开,WSL 的 interop 机制能让你从 Linux 文件沙箱里跳出去,起一个 Windows 进程;而这个 Windows 进程可以用 wsl.exe 重新进入发行版——以 root。绕的不是 Codex 的规则,是 Linux 沙箱这一层根本管不到 Windows 侧。

修法有三条,每条都值得看一眼,因为它暴露了边界的真实形状:

  • 在 bubblewrap 里遮蔽 WSL interop socket(仅当文件写入受限时)——把逃逸通道本身挡掉。
  • 在受限文件策略下拒绝 AF_VSOCK socket 与 io_uring即使普通网络访问是允许的——AF_VSOCK 是 VM 与宿主通信的通道,这条堵的是「经 VM socket 够到宿主服务」。
  • 拒绝「受限文件系统 + 不受限网络」的旧版 Landlock 配置——当 WSL interop 目录存在时,这种组合直接不接受。

注意第二条的措辞:开了网络,不等于所有 socket 类型都放行。这是很多人对「网络访问」的隐含假设,而它不是事实。

落到决策上:如果你在 WSL 上、workspace-write、且开了 network_access = true,那么在这个修复落地之前,你的文件边界不是你以为的那条线。修复已经在 rust-v0.155.0(2026-09-17 发布) 里了,Release Notes 的 Bug Fixes 明确列着:

Blocked Windows-process escapes from restricted WSL sandboxes and hardened brokered shell snapshots against credential exposure. (#44286, #43909, #44040)

所以 WSL 用户的动作很简单:确认你的版本 ≥ 0.155.0。不用碰 alpha。

macOS 的 Seatbelt 这版收得比你以为的紧:一条通配拒绝,和一条被删掉的默认放行

这一节的两个改动还只在 alpha,稳定版没有。我先说清这个前提,再讲它紧在哪。

第一条:#46583 Deny XPC service lookups in macOS Seatbelt profiles 这个 PR 的 diff 只有一行,加在 codex-rs/sandboxing/src/seatbelt.rs 里:

1
(deny mach-lookup (xpc-service-name-prefix ""))

mach-lookup 是查 Mach 服务的动作,XPC 是 macOS 上进程间通信的主要方式。这里的关键是 xpc-service-name-prefix ""——空前缀匹配所有 XPC 服务名。不是封某个服务,是一刀切。

这一行的语义是「不许查任何 XPC 服务」。我把它写成语义直读,不写成结论:它在 alpha 里会不会拦坏某个依赖 XPC 的工具,需要你自己在 alpha 上实测,我没跑,不下断言。但如果你在 alpha 上遇到某个工具链报奇怪的错,这是第一个该看的地方。

第二条:#46532 Remove com.apple.runningboard from Seatbelt platform defaults diff 同样是一行删除,从 seatbelt_read_only_platform_defaults.sbpl 里拿掉:

1
(global-name "com.apple.runningboard")

runningboard 是 macOS 管进程生命周期和调度的服务。它原本躺在「平台默认放行」清单里——那份清单的作用是:在只读受限模式下,附一段 macOS 平台策略来保住常见工具链的兼容性(官方文档把这件事描述为「append a curated macOS platform policy instead of broadly allowing /System」)。现在它从默认放行里被拿掉了,意味着依赖 runningboard 的工具链在收紧后的策略下可能不再被默认许可。

两条合起来看,Seatbelt 这版的走向是:把「平台默认」这份宽容清单往回收,同时把 XPC 整类查询关掉。 这不是小修补,是策略姿态的变化。

排查顺序给你:如果你在 macOS 上、跑的是 alpha、升级后工具链报错——先查 XPC 通配拒绝(#46583),再查 runningboard(#46532,因为前者是一刀切、影响面更宽。稳定版用户不用动,这两个改动还没落地,等它进了稳定版再评估。

凭证与补丁:两个「看不见的扩权面」正在被收口

前两节讲的是「能碰到什么」,这节讲的是「以什么身份碰」和「权限被谁悄悄放大」。

第一个扩权面:shell 快照的凭证回灌。 #44040 Harden credential handling in shell snapshots and replay 的 Why 段写得很直白:

Shell quoting can hide credentials from raw-text checks, and startup files can restore real credentials after the broker replaces them with dummy values.

两层问题。第一层,shell 引用能把凭证藏过纯文本检查——你的密钥 export SECRET="abc",换个引法、转义一下,字符串扫描就看不见了。第二层更微妙:Codex 的 broker 会把凭证换成假值再交给沙箱里的进程,但启动文件(比如 .zshrc)能在替换之后把真凭证恢复回来

修法是「不解值地解码 shell 字面量」——只解析字面量形态,不执行它,这样引号花招藏不住;并「拒绝含凭证的可执行源码,包括 alias、function、heredoc」。另外还补了:快照回放时防住经启动文件恢复凭证、以及 ENV 里的无关设置要保留(不能为了安全把环境搞坏)。

对你的影响:这是安全加固,正常用法透明。但如果你的 shell 启动文件里有 export 凭证的习惯,值得知道这条路径曾经存在。这个修复同样已经在 rust-v0.155.0 里(见上节引用的 Release Notes 同一行 #44040)。

第二个扩权面:apply_patch 会顺手扩父目录权限。 #39614 Prevent apply_patch from widening write permissions(merged 2026-08-20)的 Why 段:

Deriving permissions from the parent of an already-writable patch target can unnecessarily grant write access outside the intended workspace.

逻辑是:补丁工具在写一个文件前会去推导它需要的目录权限。如果目标本身已经在可写范围内,它还是去推了父目录权限——而父目录可能在工作区之外。修法是「对已被当前沙箱策略覆盖的目标,跳过推导额外父目录权限」,工作区之外的目标才继续请求父目录访问。

测试覆盖里有一条值得注意:「symlink 逃逸出工作区」的回归测试。符号链接把工作区内的路径指到外面,是这类权限推导最经典的绕过方式。

对你的影响:如果你自定义过 apply_patch 相关的写权限范围,值得复查一遍。这个修复 merged 于 8 月 20 日,早于 0.155.0。

顺带一个还没进稳定版的信任变化:codex-network-proxy 独立成二进制了。 #46573 新增了 codex-rs/network-proxy/src/main.rs,可以单独跑(codex-network-proxy --config <PATH>),默认起 HTTP 代理 127.0.0.1:3128 和 SOCKS5 127.0.0.1:8081。README 里有一段要读仔细:

HTTPS MITM is enabled automatically when mode = "limited" or when MITM hooks are configured. The CA private key remains in proxy memory.

limited 模式就会自动启用 HTTPS 中间人,CA 私钥留在代理内存里,公开的 CA 文件放到 $CODEX_HOME/proxy/ 供子进程信任。这不是漏洞——它是本地策略执行的必然代价,你要做域名白名单就得能看见明文。但你得知道自己开了什么:开了 limited,你就是在信任一个跑在本机的中间人。 另一个细节,allow_local_binding 省略时「MXC 为 true,其它为 false」(#46523),即托管环境默认放行本地绑定,这个默认值是在 alpha 里翻的。

你是哪类用户,这四处里该在意哪个

四个洞拆完,回到最开始那个分界:这些全是沙箱层的边界变更,审批层看不见它们。 所以你的动作不是「把审批调严一点」,而是按平台对着查。

先说最容易被带偏的一点:不要为了这几个修复去升 alpha。 alpha 一天发好几个 tag(9 月 19 日一天就有 alpha.5/6/7/8 四个),跟着它追安全修复,等于把 alpha 的回归风险一起买进来。这不是假设——稳定版 rust-v0.155.1(9 月 18 日)修的就是 alpha 里带出来的回归:

New local TUI sessions now leave reasoning summaries disabled by default, fixing request rejection by providers that do not support them. (#46467)

一个默认值改动就能让请求被拒。正确姿势是在稳定版里等这些修复落地,而不是为了几个洞去用 alpha。

按平台对号入座:

Codex 沙箱这个月补的边界:绿色两行(#44286 WSL interop 逃逸、#44040 凭证快照回灌)已进稳定版 rust-v0.155.0;橙色四行(#46583 Seatbelt XPC 通配拒绝、#46532 移除 runningboard 默认放行、#46573 网络代理独立化与 MITM、#46523 本地绑定默认翻 true)仅在 0.156.0-alpha。每行给出改动文件、适用平台与该查的配置项。

图注:按 PR 号、改动文件、适用平台与发布状态对照六处边界变更(正文按主题归为四类:WSL interop、凭证快照、macOS Seatbelt 两条、网络代理);绿色为已进稳定版,橙色为仅 alpha。数据来源:openai/codex 各 PR/commit 与 network-proxy README,核验日 2026-09-20。

你的情况 该在意哪个 动作
WSL + workspace-write + 开了网络 #44286(interop 逃逸) 确认版本 ≥ 0.155.0,已修,不用动配置
macOS,跑稳定版 #46583 / #46532 还只在 alpha,不用动,等落地
macOS,已在 alpha #46583(XPC 通配拒绝,影响面最宽) 工具链报错先查这条,再查 #46532
启动文件里 export 凭证 #44040 已进 0.155.0;考虑改用 broker/密钥管理,别长期写在 .zshrc
自定义过 apply_patch 写权限 #39614 复查权限范围,注意 symlink 指向工作区外的场景
只用只读分析、不开网络、不在 WSL 四个里三个与你无关 读一眼即可,别为此改配置

一份 alpha 升级判断清单(真要动的时候用):

  1. 锁版本号。 一天多个 tag,别追 latest。
  2. prerelease 字段。 0.156.0-alpha.8prerelease: true,API 里就能查,不用猜。
  3. body 为空时改看 commit 列表。 这个 tag 的 release body 只有一行 Release 0.156.0-alpha.8,真实内容全在 commit 里。对比范围是 rust-v0.155.0...rust-v0.156.0-alpha.8(434 commits、300 个文件)——注意这是从上一个稳定版起的全量,不是「本版新增功能」,别把 commit 数当功能量。
  4. 只 grep 边界相关的目录,别通读:
1
git log --oneline rust-v0.155.0..rust-v0.156.0-alpha.8 -- codex-rs/sandboxing codex-rs/linux-sandbox

关键词锁定:sandbox / seatbelt / landlock / bwrap / credential / escape。这四个洞全是这么捞出来的。

配置复查三问,今天就能答:

  • 我在 WSL 上开了网络吗?版本是不是 ≥ 0.155.0?
  • 我的 shell 启动文件里有没有 export 凭证?
  • 我有没有自定义过 apply_patch 的写权限范围?

最后说清本文的边界。 上面所有断言来自 commit message 与源码 diff,没有做运行时验证——我没在 alpha 上跑过 Seatbelt 那两条改动,所以「会不会拦坏某个工具」我不断言,只给排查顺序。434 commits 是从 0.155.0alpha.8 的全量,不代表本版功能量。还有一类人本文基本不适用:只做只读分析、从不开网络、也不在 WSL 上跑的——这四个洞三个跟你无关,读完知道有这么回事就行。

真正的动作只有一个:codex --version 看一眼,WSL 用户确认 ≥ 0.155.0,其他人别动。 至于「沙箱这一版在防谁」,把这几个目录的 commit log 加进你的季度巡检,比读任何一篇教程都管用。


每条断言对应哪个 commit