AREXOpen in interactive Feed

AREX Feed Article

General Analysis 检视 OpenAI Codex 现行 issue-labeler:无工具分类,标签建议与应用分离

November 13, 2026

在安全研究公司 Depi 于 11 月 11 日披露针对 openai/codex 仓库的持续集成(CI)攻击链一天后,在 11 月 12 日发布技术分析,并当天检视了 OpenAI 当前的 issue-labeler 脚本与 workflow(工作流):脚本向模型传入空 tools 列表、禁用工具选择,要求枚举约束的结构化输出并在本地校验返回的标签;workflow 则把「建议标签」与「应用标签」拆成两个权限不同的 job(作业)。GA 将此称为当前源码中可观察的设计模式,同时强调它不能证明整条披露链是在何时、以何种方式修复的。

这份分析同时转述了 :据其报告,攻击者可以通过 issue 文本操纵 issue 分类 agent,经暴露的调试管道触及 runner(CI 执行机)的 secrets,再借缓存投毒与 release workflow 行为获得对官方 @openai/codex npm 包的发布权限。GA 明确划出边界:其未复现该攻击,Depi 的报告也未证明恶意包到达用户;11 月 11 日只是披露日,不是已证实的利用或修复日期。

从一条 issue 到 runner secrets:Depi 描述的注入与沙箱逃逸

Depi 的披露题为「Backdooring the next release of Codex through GitHub Action pipeline exploit chaining」,署名作者是 Pierre Martin(网名 Worty)与 Garance de la Brosse(网名 Madder)。两人称他们在 OpenAI Codex 的开发基础设施中找到一条从一条 GitHub issue 通往官方 npm 包发布权限的路径——Depi 称 @openai/codex 包每月被下载数百万次,并推断一旦发布管道被投毒,恶意代码可借一次看似常规的更新到达开发者机器。

入口是 issue-labeler workflow。据 Depi 引用的配置,该 workflow 在 issue 被打开或被打上 codex-label 标签时触发,条件限定 openai/codex 仓库,而任何外部用户开一个 issue 就能触发;issue 标题与正文被直接拼进 Codex agent 的 prompt(提示词),经 openai/codex-action 调用,当时的配置还包含 allow-users: "*",即不把动作限定在受信任维护者范围内。

Depi 称,他们构造的注入文本伪装成「QA 工程师验证 issue」的指令:先以一段前缀闭合原 workflow 的 prompt 结构,让模型把后面整段当作新的系统指令,再用 base64 加 gzip 编码的命令躲过模型对脚本内容的识别,使其在 runner 上的 Codex 沙箱内执行任意 shell 命令。此时攻击者仍被限制在 workspace-write 模式的沙箱里:seccomp(系统调用过滤)禁掉了网络连接与 ptrace,PID namespace(进程隔离空间)只能看到沙箱自身,除当前目录和 /tmp/ 外全部只读,sudo 权限被移除。

逃逸路径在 /tmp/。Depi 解释,GitHub runner 的两个主要 .NET 进程 Runner.Listener 与 Runner.Worker 启动时会在 /tmp/ 下创建 dotnet diagnostic socket 和 CLR debug pipes;沙箱禁止 connect() 系统调用,连不上 socket,但 Linux 管道可以作为普通文件用 open() 打开——这正是沙箱允许的操作。CoreCLR 的调试传输协议握手完全无需认证,消息类型 6(MT_ReadMemory)可以任意读取进程内存。脚本选取 /tmp/ 中 PID 最高的管道,即最新派生、持有本次 job secrets 的 Runner.Worker。

接下来是纯内存导航:Depi 的脚本先通过 GET_DCB 消息从调试器控制块里拿到一个合法指针,据此向后逐页扫描 ELF(Linux 可执行文件格式)魔数定位 CoreCLR 基址,再顺着 program headers、.dynamic 段、GOT(全局偏移表)、link_map 链表找到 libc,用 GNU hash 表以约 4 次管道往返查到 __environ 符号,最后沿 environ 数组按 ghp_、ghs_、eyJ 等模式捞出 GITHUB_TOKEN 与 ACTIONS_RUNTIME_TOKEN。由于沙箱没有网络,token(访问凭证)只能通过 workflow 日志外传:GitHub 会剥离短于 1,024 字符的输出消息,脚本把 token 切成 1,024 字符的分段;注入文本还让 Codex 睡 5,000 秒,因为提供缓存读写权限的 token 只在 workflow 运行期间有效。

从缓存投毒到 npm 发布权限:Depi 描述的最后一公里

token 本身还不构成发布权限。Depi 把目光转向 openai/codex 仓库里的另一条 workflow:rust-ci.yml 权限较宽,包括 Actions: write 在内,并在 job 中途恢复缓存、随后运行 Python 测试。Depi 指出,GitHub 缓存是一个包含绝对路径的 tar 归档,恢复时不检查内容,缓存投毒因此可以把文件写进 runner 的任意路径。他们向与缓存 key 匹配的恶意缓存项里放了一个 Python 测试文件,落点为 /home/runner/work/codex/codex/tools/argument-comment-lint/test_depi.py,用它滥用 rust-ci.yml 的 Actions: write 权限。

由于沙箱内没有网络,Depi 在自己控制的仓库里使用这些 token——GitHub API 仍会把它识别为 OpenAI 的有效凭证——并借助 Adnan Khan 开发的 Cacheract 工具完成缓存投毒。Depi 还使用了 Khan 分享的另一个 GitHub 漏洞:release: delete 触发器配合 id-token: write 权限,可以请求 OpenID 权限、代目标组织窃取 token。Depi 先用 Actions: write 权限在 openai/codex 仓库创建一个假的 Codex release 引开发者删除;当开发者删除这个 release 时,陷阱 workflow 被触发,攻击者拿到 OpenAI 的 OpenID token,再向 GitHub 换取 npm 信任的签名 JWT,从而获得发布恶意版本的能力。Depi 在披露中注明,这套缓存技术对 issue 评论、pull_request_target 等高风险触发已不再有效。

GA 当日检视的入口代码与职责边界

GA 在 11 月 12 日检视的是这条攻击链入口对应的现行代码:与 。截至 11 月 13 日,这两个文件仍可在 openai/codex 仓库 main 分支查看,内容与 GA 的检视结论一致:脚本第 83 行写有 "tools": [],TYPES、PRODUCTS、AREAS 三组标签目录均以枚举形式约束模型输出;workflow 里 gather-labels job 只持 contents: read 权限,持 issues: write 的 apply-labels job 独立运行,通过 gh issue edit 应用标签,触发条件与 Depi 引用的旧配置相同——issue 被打开,或被打上 codex-label 标签。

GA 给出的设计逻辑是:一个 issue 分类器应该能够把 issue 分错类,而不因此获得发布软件的途径。如果一个 workflow 只做 issue 文本分类,就用不带工具的模型请求加固定输出目录完成——这拿掉了模型的命令执行路径,保留分类任务本身;恶意 issue 仍可能扭曲答案,但它能扭曲的只是标签值,由应用代码决定哪些输出可接受。按 GA 画的职责表,分类 job 只许读 issue 文本并返回固定目录中的值,shell 工具、仓库写入、缓存访问、release 凭证都留在其权限之外;应用 job 校验目录值并更新触发的 issue;构建发布 job 在发布策略下构建经批准的修订,triage(issue 分类)输出不得被当作代码执行,不受信缓存内容与 issue 控制的 workflow 定义同样被排除。在这一设计里,第二个 job 使用的仓库、issue 编号、脚本路径或权限集都不由模型选择,任何会启动特权自动化的标签都不得进入分类目录。

缓存与发布权限要分开核查:GA 给出的逐项路径

针对 Depi 链路用到的缓存环节,GA 的建议是把缓存访问与仓库权限分开检查。GitHub 已在 中为符合条件的不可信触发引入默认分支只读缓存 token:触发事件不可信、且执行与缓存上下文来自共享默认分支 SHA,两个条件同时满足才发只读 token;写入被拒时 actions/cache 只在日志里记一条消息并继续,恢复不受影响。9 月 10 日,显式 在 github.com 上正式可用:read、write、write-only、none 四档,job 级设置覆盖 workflow 级,由缓存服务强制执行并贯穿可复用 workflow;对低信任事件,显式声明 write 或 write-only 可以覆盖只读默认,GitHub 会为此加警告标注。GA 提醒,这条规则同时取决于触发事件与执行、缓存上下文,不要假设每种触发都有相同的缓存访问。

GA 建议:不需要依赖缓存的 triage workflow 直接设 cache-mode: none;逐个检查 job 覆盖,包括对可复用 workflow 的调用——job 级可以改写顶层限制,后者不足以证明安全;可以删掉未使用的缓存步骤,但要保留服务级限制,因为仅省略 actions/cache 步骤并不表达拒绝缓存访问。

发布侧,GA 介绍 npm trusted publishing 把发布绑定到配置的仓库与 workflow(可选 environment),以 OIDC(OpenID Connect,开放身份认证协议)认证取代长期发布凭证,但身份校验并不决定 workflow 的输入或构建产物是否可信。GA 建议记录接受的 workflow 文件名、environment、允许的发布动作和残余的 token 发布路径,再追溯哪些脚本、产物、缓存和触发器能到达该 workflow;做暴露评审时保留 workflow 修订、触发身份、run ID(运行编号)、解析出的 action/CLI 版本、缓存恢复与保存记录以及包发布记录。GA 还提醒:可疑 issue 是一条线索,不是包被攻破的证明。

对确实需要 agent 的任务(代码审查、修复),GA 引用 OpenAI Codex Action 安全文档的建议:区分命令权限与 Codex 进程权限,用窄权限配置配合 drop-sudo 或专门配置的非特权用户;被加载的 Codex home 配置是受信任输入,不要取自被分析的不受信 checkout;把 Codex 放在 job 最后运行,因为它可能留下进程、被改动的 action 文件或钩子,特权后续工作应放在全新 runner 上;release secrets 要挡在整个 agent runner 之外,而不只是某个子进程的环境变量。

未给出的 CLI 版本与未决的赏金评定

GA 还标出一个具体空白:Depi 的报告没有给出能解决这条 CI 链的客户 CLI 版本——GA 特意提醒,其早前另一篇关于 Codex 沙箱升级的指南中的 CLI 版本阈值不适用于本次披露。

Depi 称,两人通过 Bugcrowd 向 OpenAI 提交了漏洞和完整的概念验证(proof of concept),OpenAI 随即部署修复并确认了问题的严重性;OpenAI 给出 1,000 美元初始赏金,Depi 表示,OpenAI 安全政策对严重发现的奖励上限为 100,000 美元,期待最终评定反映这个漏洞的影响。对用户,Depi 的建议是:核心 bug 已修,无需自行打补丁,但对开启自动更新的工具保持一段短暂冷却期——Codex CLI 是「运行即功能」的 agent,开启自动更新的用户没有版本固定可审、没有 diff 可读;Depi 还引用了 Shai-Hulud 蠕虫——一个自我复制、收割凭证并借凭证继续扩散的 npm 供应链攻击——作为这类风险的先例。截至 Depi 披露发布时,这份报告仍在 OpenAI 的进一步评审中。

参考链接