你可能也这么干过:在一台 Linux 机器上跑 Codex,把 .env、~/.aws/credentials、一份带客户数据的目录写进沙箱的 deny 规则,然后把权限开到全自动,起身去倒杯水。心里清楚这些路径它碰不到。

规则本身没写错。执行规则的却是一条命令链,链子上有一环,要去问你机器上的配置。

10 月 8 日发布的 Codex CLI 0.162.0 补掉了两个这样的环。

沙箱不认识文件,得先问别人一遍#

Linux 上的 Codex 沙箱由 bubblewrap 搭起来,文件系统策略要靠一份具体的文件名清单才能生效。这份清单怎么来?把 deny glob 一条条展开,问 ripgrep 哪些文件命中。

内部首选的做法是 rg --files --hidden --no-ignore --glob ...,机器上没有 rg 时回退到 Codex 自己实现的 globset walker。两条路都依赖同一份由外部程序给出的文件列表,沙箱的安全边界就落在它上面。

而 ripgrep 会读用户配置。

PR #51527(10 月 7 日合入)的描述写得很直白:用户的 ripgrep 配置可以压制这份用来构造 Linux 沙箱 deny 掩码的文件列表,RIPGREP_CONFIG_PATH 里的 --quiet 会让命中 deny glob 的文件不被遮蔽。

换成白话:你在沙箱里写「这个目录不许看」,Codex 转头问 rg「这个目录下有哪些文件」,rg 因为你自己的配置闭上了嘴,回来说「一个都没有」。清单空了,掩码也就空了。

修的办法是从根上断掉这种耦合,给那次调用加上 --no-config,让它无论环境变量指向哪份配置都不读。

顺带说一句 ripgrep 的脾气:它不会去任何预设目录翻配置,只有你主动把 RIPGREP_CONFIG_PATH 指过去,它才会读。

这个修复的回归测试值得看一眼。测试构造一份含 --quiet 的 ripgrep 配置,然后验证命中 deny glob 的文件仍然不可读不可写,没命中的文件照旧能访问。

这里有个通用做法:凡是安全相关的内部调用,只要它依赖的底层工具是可配置的,就必须显式忽略用户配置。

第二个洞:沙箱还没搭好,启动器先在 PATH 里挑错了#

bwrap 是真正搭沙箱的那个程序。Codex 的挑选规则是从 PATH 里取第一个 bwrap,只排除当前工作目录,一个都找不到就用自带的副本。

PR #51211(10 月 6 日合入)点出问题是时序:探测候选程序的动作发生在 confinement 之前。只排掉当前工作目录,等于把其他可写目录里的候选全留在池子里,它们会被放在沙箱外面执行。

修复做了几件事:按文件系统策略和实际用户权限过滤 PATH 候选,可写的祖先目录、符号链接指向的根、整盘可写都算数;系统里受保护的那份保留,可被替换的路径直接剔除;挑启动器时用命令自身的权限,位置放在 proc 挂载预检之前;自带的 bubblewrap 兜底留着。

要慌吗:条件不宽,但版本得升#

两个洞都要求本地条件凑齐。ripgrep 那个得有人给 RIPGREP_CONFIG_PATH 指过一份会压制输出的配置;bubblewrap 那个得 PATH 上有个可写的 bwrap,还排在受保护的那份前面。

发布说明和 PR 里都没有野外利用、远程攻击或者通用绕过的记录。官方把这四个 Linux 沙箱条目列成一组相关修复:#50059(启动时存在多个被拒文件)、#51211、#51407、#51527,没当成一个漏洞来发。

成因值得记一下。为了颜色、smart-case、--hidden 去写一份 ripgrep 配置的人太多了,写的时候不会有人想到,这份配置会跑去关自己沙箱的黑名单。

还有一个第三方报告可以放进提醒里:0.160.1 在展开 deny glob 时,碰到第一个 ripgrep 列不出来的目录就放弃,生成的配置把 Linux 上的每条命令都拦住。那是老版本上的事,0.162.0 的发布说明没声称修了它。升级之后,建议拿自己的 deny-glob 配置实测一遍。

同一个版本里的另一半:配置层多出两个坑#

0.161.0 和 0.162.0 还有两处改动伸进了你的 config.toml。

第一处,默认模型换成 GPT-6.1 Sol。发布说明的原话是「bundled 和 Amazon Bedrock 目录里的默认模型」,其他方式接的模型有没有跟着变,它没说。想钉住版本,就在 ~/.codex/config.toml 顶层写一行:

model = "gpt-6.1-sol"

官方参考还提示要「把这个值设为你已登录账号或工作区可用的模型」。工作区没开这个模型,写进去也不会生效。Bedrock 那边同时多了 multi-agent V2 和 Ultra reasoning。

第二处,Daybreak 变成必须显式打开。这条最容易让现有配置静默失效:

[features]
cli_daybreak = true

daybreak = true 单独放着什么也不做。默认状态下控件和指示器是藏起来的,/daybreak 用不了,连已经保存的 Daybreak 线程也不再自动走 Cyber 路由。你之前存的偏好没有丢,把 cli_daybreak 加回去就恢复。codex exec --cyber-access-program 是新增的按轮指定通道,这个参数在 cli_daybreak 关着的时候也能用。

有意思的是文档没跟上。截至 2026 年 10 月 12 日,官方配置参考的 features.* 表里查不到 cli_daybreak,键名只出现在发布说明里。表里列着的是 features.apps、features.hooks、features.memories、features.multi_agent、features.network_proxy。worktree 功能的开关键名,以及 TUI 里滚轮速度的 tui.mouse_scroll_speed,同样查不到。

也就是说,这次改动里最影响日常使用的三个键,只能从发布说明里学。

升级之后自己动手验一遍#

  • codex --version 看版本,低于 0.162.0 就没有这两个沙箱修复。
  • 按当初的安装方式升级。npm 装的包名是 @openai/codex。
  • Linux 或 WSL2 上跑 command -v bwrap,确认它落在 root 拥有、不可写的系统路径里。WSL2 用的是 Linux 那套实现,需要发行版内装了 bubblewrap。
  • ripgrep 配置不用删。修复之后,Codex 在展开 deny glob 时会忽略它。
  • 随便挑一个命中 deny glob 的文件,在 Codex 会话里试着读一下。这一步是原文作者自己提的合理性检查,不是官方步骤。

最后#

这两个洞本身不吓人,条件窄,也没人真拿它做文章。值得看的是它暴露的结构:一个 agent 的安全边界,往往不写在策略文件里,而是散落在「它调用谁、谁读了哪个环境变量、PATH 上第一个同名程序是谁」这些地方。

策略是抽象的,执行是具体的。你读 Codex 的沙箱设计,看到的是 deny 规则;你判断它实际拦住了什么,要看那次 rg --files 究竟返回了几行。

0.162.0 值得升的原因不在新功能上。它的价值在于官方承认了搭沙箱的那几行代码里有假设。

而 0.161.0 那两条配置改动指向另一头:默认值会变,文档会滞后。凡是靠默认值活着的工作流,都得留一条自己能验的路。

参考来源: