文章总结: 文章剖析OpenAICodex两处沙箱逃逸漏洞:Overpatch借/tmp项触发补丁工具按父目录推导权限,将写权限撑至根实现持久化;Heapjack在read-only模式经堆快照提取node_repl共享堆中令牌,伪造管道请求取得沙箱外执行。两者成因均为边界判定组件位于边界内部,报告八天内修复,CLI补丁可核查而Desktop闭源无据,建议核对边界位置而非依赖档位名称。
综合评分: 90
文章分类: 漏洞分析,AI安全,漏洞POC,红队,代码审计
Codex 沙箱逃逸双漏洞剖析
Ots安全
2026年9月17日 12:41
广东
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
威胁简报
恶意软件
漏洞攻击
2026 年 8 月 12 日,安全厂商 Accomplish 向 OpenAI 提交了两份报告,指向同一个产品的两个不同沙箱模式。一份针对开源的 Codex CLI,在 workspace-write 模式下让补丁工具拿到整盘写权限;另一份针对 Codex Desktop 内置的一个 JavaScript 工具,在被认为最严格的 read-only 模式下取得未沙箱化的命令执行。两个问题相互独立,报告方给了它们代号 Overpatch 与 Heapjack,修复在八天内上线,细节于 9 月 15 日公开。
这两个漏洞的成因在结构上高度相似:负责执行边界判定的东西,本身运行在被边界约束的对象内部。本文按公开证据逐层核对两个漏洞的版本边界、成因、补丁痕迹,以及它们与同月另一批已编号漏洞之间的差异。
一、先看问题:两个逃逸与一行变更日志
1.1 报告方给出的事实
全部技术描述来自 Accomplish 的公开文章《Escaping the OpenAI Codex sandbox, twice》,作者是该公司首席安全研究员 Oren Yomtov。文中给出的事实有三条:
- 两个漏洞于 2026 年 8 月 12 日同时提交给 OpenAI
- 两者都在八天内修复
- 两者都无需用户审批,其中 Heapjack 在
read-only模式下仍然走通
文章自述其产品 Accomplish 把整个 agent 放进虚拟机运行,模型、bash、git 以及它们派生的全部进程都在 guest 内,真实凭据不进入 guest。这段自述与两个漏洞的技术分析在逻辑上互相承接,但它是厂商的产品陈述,不是漏洞证据。
| 日期 | 事件 | 可核查来源 |
| — | — | — |
| 2026-08-07 | Codex CLI 0.147.0 发布 | GitHub 发布记录 |
| 2026-08-12 | Accomplish 向 OpenAI 提交两份报告 | 报告方自述 |
| 2026-08-18 | Codex CLI 0.148.0 发布(22:26:03Z) | GitHub 发布记录 |
| 2026-08-20 | 修复 PR #39614 合并(06:23:27Z)、#39659 合并(08:12:30Z) | GitHub PR 记录 |
| 2026-08-20 | Codex CLI 0.149.0 发布(21:04:55Z) | GitHub 发布记录 |
| 2026-09-15 | 报告方公开技术细节 | 报告方文章日期 |
报告到修复是 8 天,与报告方”八天内修复”的说法吻合。中间的 8 月 18 日有一个版本发布,该版本仍属受影响范围。
二、影响范围:版本区间与沙箱模式
2.1 CLI 版本区间与 Desktop 构建号
Overpatch 的影响范围是 Codex CLI 0.148.0 及更早,修复版本 0.149.0。Heapjack 的影响范围是 Codex Desktop 26.623.141536 及更早,修复版本 26.818.21641。
Desktop 是闭源产品,没有公开的构建清单,但它的版本号格式可以从公开记录里读出来。多个公开渠道出现了同一格式的版本号:社区反馈帖记录 Windows 端 26.818 版本线依次为 26.818.3698.0、26.818.4152.0、26.818.5229.0;GitHub issue 记录 macOS 端 26.818.41509 的使用时段为 8 月 24 至 26 日,之后是 26.820.60940 与 26.825.31414。官方变更日志里也出现过 26.908 这样的版本号。三段式版本号中的第二段与发布日期对应,因此 26.623 对应 6 月 23 日构建,26.818 对应 8 月 18 日构建。
这条对应关系是从公开版本号序列推断的,不是官方文档的声明。可以确定的是受影响区间跨越约两个月,从 6 月下旬到 8 月中旬的 Desktop 构建都需要处理。
2.2 沙箱模式并不构成边界
两个漏洞恰好覆盖了 Codex 沙箱配置的两个主要档位。
Overpatch 在 workspace-write 模式下工作。这一模式的语义是”智能体只能在 workspace 目录内写文件”,报告方验证过一条绕过尝试会被拒绝:直接写 $HOME 的命令不通过。但该模式下的补丁工具走的是另一条权限推导路径,那条路径没有遵守同一个边界。
Heapjack 在 read-only 模式下工作。这一模式被产品描述为最严格的沙箱档位,语义是”智能体什么都不许写”。Heapjack 的终点却是未沙箱化的命令执行。
两个档位各被打开一次,说明档位名称与实际边界之间没有必然联系。判定脚本或部署清单如果只登记了”我们用的是 read-only”,这里拿不到任何安全结论。
2.3 node_repl 的装载状态
Heapjack 的前提条件是 Desktop 安装时写入的一段配置。Codex Desktop 在安装阶段向全局 ~/.codex/config.toml 写入 [mcp_servers.node_repl] 段,报告方明确指出这段配置没有 opt-in 环节,也没有可以关掉的开关。
因为写的是全局配置文件,所以纯命令行的 codex 也会加载同一个工具。这一条把影响面从 Desktop 用户扩展到同时装了 CLI 的用户,判定时不能只按”我用的是 CLI,没有装桌面版”排除。
该工具本身的权限也不小:它是一个原生 Rust 程序,运行在沙箱之外,能打开应用程序、连接 Unix 套接字、写配置文件。它通过 Codex 沙箱启动一个 Node 进程,JavaScript 侧确实在沙箱内运行,问题出在这个 Node 进程与 Rust 父进程之间的信任判定上。
2.4 一次打开与一次提问
两个漏洞都不要求攻击者事先拿到目标主机上的任何权限,也不需要管理员身份。触发动作是用户打开一个仓库并向 Codex 提问。
对 Overpatch 而言,攻击者要控制仓库里能被补丁工具处理的内容;对 Heapjack 而言,攻击者只需要仓库里的说明内容能让模型照着执行。两者的共同点是全程没有审批提示出现,屏幕上也没有可见迹象。
有一处边界要写清楚:报告方在 Heapjack 一节给出的表述是”未沙箱化的命令执行”,其严重性对照是”你自己把沙箱关掉、亲手运行那个脚本时它会获得的权限”。这是本地用户权限范围内的执行,不是把整台机器交出去。至于是否会进一步放大,取决于该用户在主机上的既有权限,公开信息未说明报告方做过这方面的验证。
四项判定合起来看,真正筛掉实例的是 2.3 与 2.4 两项:只装了 CLI 但从未升级到 0.149.0 且会打开陌生仓库的环境,是这次修复最该覆盖的对象。
三、原因导致:从自身输入推导的权限
3.1 父目录推导的权限
apply_patch 是 Codex 提供给智能体编辑文件的工具。在 workspace-write 模式下,写入允许范围之外的路径需要临时申请额外权限,而这段权限申请的逻辑取的是每个补丁路径的父目录。
于是路径选择直接决定了授权范围。补丁里出现 /tmp,父目录是 /,工具就把整盘写权限纳入本次授权。报告方的原话是:”Name /tmp and it grants write access to /.”
图的中间一段是整个缺陷的骨架:parent("/tmp") 被算成 /,随后写权限集合被扩展为 [ / ]。当权限是从被处理对象自身携带的输入推导出来的时候,输入里出现一个足够靠近根的位置,边界就退到了根。
这一节要和 2.2 对照读。同一个模式下,直接写 $HOME 的命令被拒绝,而经由补丁工具的写入被放行。两者走的是两条独立的判定路径,其中一条没有和另一条对齐。
3.2 从过滤器插入的位置说起
修复提交是 #39614 Prevent apply_patch from widening write permissions,合并时间 2026 年 8 月 20 日 06:23:27Z。它的说明只写了两个短句:
Why: Deriving permissions from the parent of an already-writable patch target can unnecessarily grant write access outside the intended workspace.
What changed: Skip targets already covered by the active filesystem sandbox policy before deriving additional parent-directory permissions.
代码改动是在权限推导的迭代链上加一个过滤器,位置在 .iter() 之后、.map() 之前:
let write_paths = file_paths .iter() // Skip already-writable targets before deriving parent permissions. // Otherwise, a writable directory could grant access to its parent. .filter(|path| { !file_system_sandbox_policy.can_write_path_with_cwd(path.as_path(), cwd.as_path()) }) .map(|path| { path.parent() .unwrap_or_else(|| path.clone())
语义是:如果一个目标已经落在当前沙箱策略允许写入的范围内,就不要再为它推导父目录权限。反过来读这条规则,正好说明了旧实现的毛病所在:一个本来就可写的目标,会因为推导父目录而把权限扩到它的上一层。
注释第二句写得很直白,”否则,一个可写目录可以把它父目录的权限授予出去”。这条注释把根因固定在了源码里,不需要再靠反编译或推测补足。
3.3 令牌与不受信代码同堆
Heapjack 的成因与权限推导无关,它出在内存布局上。
同一个 Node 进程里有两个 V8 vm 上下文。受信任的那个运行 OpenAI 自己的代码,并持有一个每次运行重新生成的随机令牌;不受信任的那个运行智能体生成的代码,不持有令牌。两者共用一个 Node 进程、一个 V8 isolate、一份堆内存。而那个令牌,就是堆里的一个字符串。
报告方的表述是:”One Node process, one V8 isolate, one heap shared by both contexts. The token is a string in that heap.”
信任判定的设计本身并不粗糙:受信上下文用一个随机令牌证明身份,令牌每次运行重新生成。问题在于这个秘密与要防的对象放在同一块可读内存里。隔离措施的价值取决于秘密存放的位置,而不是随机性强度或生成频率。
3.4 一条管道承载两种请求
沙箱内的 Node 进程与沙箱外的 Rust 父进程之间靠管道通信。请求以 JSON 行写入 fd 1,也就是 stdout,两个上下文共用这一条管道。
父进程这一侧的校验逻辑是:读一行 JSON,检查其中的令牌,通过则执行。不受信代码要做的事情,是按同样的格式把自己的请求写进同一条管道。
到这里 Heapjack 的两级结构清楚了:一处是秘密可读,一处是通道共用。报告方对这条链的概述是”Knowing that one string is the whole boundary.”取得那一串字符就等于取得了边界。
从防御角度看,这一节的推论比上一节更硬:只要信任判定依赖的是”调用方知道某个秘密”,而这个秘密与调用方处在同一个可读内存里,那么无论校验代码写得多严密,判定结果都不再有意义。校验逻辑本身没有缺陷,失效的是它成立的前提。
四、漏洞触发:两条链的工程路径
4.1 诱饵项如何撑大授权
Overpatch 的利用构造只需两处改动。
第一处把内容写到 .zshrc:通过一个指向 $HOME 的符号链接落盘,终端下次打开时会加载它。第二处是 Add File: /tmp,这一处必然失败,因为不能把一个文件写到目录之上。报告方对这一处的定性很明确:”It is in the patch only to widen the grant.”它的存在不是为了写入,而是为了把授权范围撑到根。
把第二处删掉,第一处的写入就会被拒绝。这一对对照关系是 Overpatch 最关键的验证点:撑大授权的那一项和落地写入的那一项,功能上完全分离。
利用链的终点不是当下的即时控制,而是一次持久化:写入 .zshrc 之后,用户下次打开终端时,那段内容会在沙箱之外被加载。报告方的表述是”Write .zshrc and the next terminal you open runs it unsandboxed.”
4.2 从堆快照到伪造请求
Heapjack 的利用分两步,第一步是取令牌。
不受信代码调用 v8.getHeapSnapshot() 拿到堆快照,然后枚举其中所有符合 UUID 形状的字符串。判定哪个是正确令牌靠的是有差别的回应:错误的令牌返回 not authorized,而正确的令牌配上一个不合法参数会返回一个真正的参数校验错误。两种回应之间的差别,让枚举可以收敛到一个字符串。
图里两条箭头标出了这条链的两个动作:一条从不受信上下文指向共享堆,取回令牌;另一条沿着 fd 1 向上,把同格式的请求写进父进程正在读的那条管道。
第二步是发出请求。拿到令牌后,不受信代码按 JSON 行格式把自己的请求写入 fd 1,父进程读取、校验、执行。父进程最终调用的是系统 open 命令,于是被启动的应用由 launchd 拉起,在 Codex 进程树之外运行,从而脱离沙箱。
同一个令牌还能做两件延伸的事:连接任意 Unix 套接字,其中 Docker 守护进程套接字是最直接的候选;以及编辑全局 ~/.codex/config.toml。报告方把 Proof of Concept 的终点定在启动应用这一步。这几项能力叠加起来,说明取得的不是某一个工具的调用权,而是父进程的全部能力。
4.3 read-only 为什么没有拦住
Heapjack 全程在 read-only 模式下运行,报告方明确写了”Codex never asked to approve anything.”
要理解这一点,需要区分沙箱约束的对象与逃逸动作经过的位置。read-only 约束的是智能体直接发起的文件操作;Heapjack 借助的是沙箱外那个原生父进程的能力,沙箱内的代码只负责向管道写一行格式正确的 JSON。约束没有失效,它作用在了一条与逃逸路径不相交的地方。
这也是 2.2 那一条判定项要单独列出的原因。把 read-only 视作安全档位,等于假定所有逃逸动作都必须经过文件系统写操作,而这一步恰好绕开了这个假定。
4.4 两条链的可核查程度不同
CLI 侧的补丁可查。合并时间、改动代码、说明文字、回归测试都在公开仓库里。Desktop 侧不可查,产品闭源,官方变更日志中没有对应条目。
这个差别直接决定了本文的证据强度,也决定了对修复做复现验证的可行程度:Overpatch 可以按补丁与测试用例直接确认修法方向,Heapjack 只能以报告方的描述为上限,无法独立验证改了什么。
由此得出一条写作纪律:涉及 Heapjack 修复细节的表述,只能写”报告方披露了该问题并在八天内得到修复”,不能写”OpenAI 把令牌迁出了共享堆”这样的话。后者在公开信息里没有依据。
五、补丁分析:可见与不可见的两侧
5.1 修复在完整列表里的位置
Codex CLI 0.149.0 的发布说明分三个摘要小节:New Features、Bug Fixes、Documentation。安全相关的改动没有出现在任何一个小节里。#39614 只出现在说明末尾的完整提交列表中,形式为一行简短标题。
同一批还有 #39659 Harden unsandboxed patch filesystem access,合并时间 8 月 20 日 08:12:30Z,比 #39614 晚约两小时。它的说明针对的是另一个问题:补丁路径在完成校验之后被替换成符号链接,使已经批准的补丁操作落到另一个文件上。修法是给执行器的文件读写、元数据查询、目录创建与删除加上不跟随符号链接的选项,并在绕过沙箱运行 apply_patch 时默认关闭符号链接跟随。
两个提交合起来看,CLI 侧这一轮收紧的是同一类东西:补丁工具自行推导权限的过程,以及这个推导结果在被使用之前可能发生的变化。
5.2 回归测试复刻了利用手法
#39614 带进来的测试是用例级别的证据。其中一个集成测试名为 apply_patch_cli_does_not_widen_permissions_for_workspace_directory_target,测试里使用的补丁内容如下:
*** Begin Patch*** Update File: link.txt@@-original outside content+pwned*** Add File: ../work+decoy*** End Patch
测试代码里的注释写着:”The second hunk names the already-writable workspace directory. It must not grant access to that directory’s parent before the first hunk runs.”
结构与报告方的描述同形:第一处改动经由符号链接落到工作区之外,第二处是一个不承担写入功能的项,存在的目的只是把权限推导往上抬一层。测试里的诱饵指向工作区目录而不是 /tmp,方向与利用构造有差别,但机制是同一个。厂商在修复时把这一类构造固化成回归用例,等于给这个缺陷的形状留下了第二份可核查的记录。
这也是本文能给出的最强证据。报告方没有公开 PoC 仓库,但厂商测试用例把机制复述了一遍,而它出现在官方源码仓库里。
5.3 闭源侧的公开痕迹
Desktop 侧的修复没有留下可核查的痕迹。逐项核对的结果是:官方变更日志中检索不到 26.818 或 26.623 这两个版本号,也没有任何一条提到该修复;openai/codex 仓库的安全通告页维持 1 条旧记录;编号层面同样为空。
修复确实发生了,这一点由报告方的时间线、受影响范围的上限构建号与修复构建号之间的落差、以及问题的不可复现性共同支撑。但”修了什么”这件事没有公开。
5.4 同类缺陷的历史线
把这个漏洞放回 Codex 的历史里,能看到一条重复出现的形状:边界参数取自模型可以影响的位置。
2025 年 9 月发布的 GHSA-w5fx-fh39-j5rw(CVE-2025-59532)记录的是 0.2.0 至 0.38.0 的问题,模型生成的当前工作目录被当成沙箱的可写根,导致写操作越出用户启动会话时所在的那个目录。修复方式是把这个边界改成以用户实际启动会话的位置为准,并在使用前做规范化与校验。官方通告对它的定性是”沙箱配置逻辑中的一个缺陷”。
2026 年 4 月的 ZDI-26-305 同样被描述为沙箱绕过,触发条件写的是目标用户需要让 Codex 处理一个包含恶意 JavaScript 的仓库。
本篇的 Overpatch 是同一个母题的第三次出现,只是取参数的接口从工作目录换成了补丁里的文件路径。三者的共同点是边界判定所需的输入,落在模型或仓库内容可以触及的位置上,而边界本身没有对这份输入做独立于它的校验。修复方向的对应关系也很整齐:2025 年那次把边界的来源从模型输出改回用户输入,这次则是在推导前先与既有策略比对一次。
#39659 修的是另一类问题,符号链接在通过校验之后、被使用之前被替换,属于检查与使用之间的时间差。它与 Overpatch 没有因果关系,但两者出现在同一天的提交序列里,说明这一轮加固同时覆盖了权限推导与推导结果的使用环节。
这条历史线的实用含义是:判定一个智能体工具的文件边界是否可靠,不能只看它有没有沙箱、默认开不开,要看它允许哪些输入参与边界计算,以及边界在被使用之前有没有再校验一次。这也解释了为什么这类缺陷会在同一个产品上反复出现,它们不是同一段代码的同一处疏漏,而是同一类设计假设的不同落地。
5.5 一处外部转述的偏差
中文技术社区对本次披露的转述中,出现了一句源文章没有的表述:称 OpenAI 收到报告后”对 apply_patch 的父目录授权逻辑与 node_repl 的令牌存放机制分别进行了调整,后者将受信代码迁出了与不受信代码共享的堆”。
前半句可以核实,方向与 #39614 的改动一致。后半句没有任何公开依据:Desktop 闭源,没有补丁可看,官方没有任何说明。这是一处转述方为闭源产品补出的修复细节。写这类内容时,把”厂商修了”与”厂商这样修”分开处理,比补齐细节更重要。
六、结束语
把两个漏洞并列来看,它们的技术细节分属权限推导与内存布局两个领域,成因却是同一条:执行边界判定的组件,被放在了边界内部。apply_patch 依据自己被交予的输入推导自己的权限范围;node_repl 把用来区分信任的秘密,与不受信代码放在同一块可读内存里。报告方对这一点的概括是”Both sandboxes were told, from the inside, to let something through.”
这一条对选择工具的意义有限,对部署方式的意义很清楚。沙箱档位的名称、是否默认开启、产品文档如何描述隔离强度,都不能替代对边界位置的核对。判定项应当落到具体的问题上:谁在决定允许什么,决定这件事的东西和它要防的东西之间隔着什么,两者是否共享内存、管道或配置文件。
本次两家完成了一次完整的修复与披露:报告在八天内得到处理,技术细节在修复后公开。可核查程度上的落差同时存在,CLI 侧的补丁、说明与回归测试都在公开仓库里,Desktop 侧留下的只有修复构建号。就这一批报告而言,OpenAI 的反应速度没有问题,能见度上有缺口。唯有把边界判定从被约束对象内部搬到外部,把已修复这件事写进可检索的记录里,构建智能体平台的团队才不必依赖日志里一行的位置去判断哪一次升级是必须的。
七、参考
报告原文
- Accomplish Blog:《Escaping the OpenAI Codex sandbox, twice》,作者 Oren Yomtov,2026 年 9 月 15 日
上游修复(Codex CLI,开源仓库)
- 修复提交说明:
openai/codexPull Request #39614,标题Prevent apply_patch from widening write permissions,合并时间 2026-08-20T06:23:27Z,merge commit530c1aed58b88060cfe851005c9dae3a89f44ad0 - 关联提交:
openai/codexPull Request #39659,标题Harden unsandboxed patch filesystem access,合并时间 2026-08-20T08:12:30Z,merge commite3e5ad28470f6a225301518c30a66e749a880164 - 改动文件:
codex-rs/core/src/tools/handlers/apply_patch.rs、apply_patch_tests.rs、codex-rs/core/tests/suite/apply_patch_cli.rs - 发布版本:
rust-v0.149.0,发布时间 2026-08-20T21:04:55Z - 中间版本:
rust-v0.148.0,发布时间 2026-08-18T22:26:03Z
编号与通告核查
-
openai/codexGitHub 安全通告页:当前 1 条记录,GHSA-w5fx-fh39-j5rw(CVE-2025-59532),2025-09-19 发布
-
OSV 查询 npm 包
@openai/codex:返回 CVE-2025-59532、CVE-2025-61260,均不对应本文两个漏洞 -
同月 OpenAI 编号对照:CVE-2026-19590、CVE-2026-19591、CVE-2026-19592、CVE-2026-19593,均于 2026-09-01 发布,修复版本为 CLI 0.131.0 与 Desktop 26.519.x
-
相关历史记录:ZDI-26-305(2026 年 4 月,Codex 沙箱绕过)
第三方分析
- Pick Right:《Three research teams escaped every major coding agent’s sandbox in four months》,2026-09-13,讨论本次披露与同期其它厂商披露在编号与公告上的差异
- CN-SEC 中文网:《OpenAI Codex 沙箱两度被突破:Accomplish 披露两个高危漏洞》
地址清单
报告原文https://accomplish.ai/blog/escaping-the-openai-codex-sandbox-twice/
上游修复 PRhttps://github.com/openai/codex/pull/39614https://github.com/openai/codex/pull/39659
上游发布https://github.com/openai/codex/releases/tag/rust-v0.149.0https://github.com/openai/codex/releases/tag/rust-v0.148.0
安全通告页https://github.com/openai/codex/security/advisories
同月编号对照https://www.cve.org/CVERecord?id=CVE-2026-19591https://www.cve.org/CVERecord?id=CVE-2026-19593
第三方分析https://pick-right.com/news/coding-agent-sandbox-escapes-beltdown-patch-latency-not-a-vendor-trait-2026-09-13https://cn-sec.com/archives/5434456.html
END
公众号内容都来自国外等平台- 搜索的内容通过结合编写 –
提供整洁 – 广告已关
公众号 | AnQuan7 (Ots安全)
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Ots安全 《Codex 沙箱逃逸双漏洞剖析》