Codex 只读审查的权限边界任务文件是谁写的一次 Codex 审查没有修改仓库代码却留下了新的任务 JSON。看到这种现象先别把它判成“只读沙箱失效”。更值得追问的是文件由哪个进程写入read-only又设置在哪一层在codex-plugin-cc的同一份源码中可以同时找到两种操作审查线程向 Codex App Server 请求只读沙箱插件自身用 Node.js 的文件接口保存任务。这两件事能够同时成立。模型任务的只读约束不能直接推广成整个插件及其工具都没有写入能力。本文核对的是官方仓库提交db52e28f4d9ded852ab3942cea316258ae4ef346核对日期为 2026-09-20。下面是源码路径分析开头的文件变化是据此构造的排查情境不是一次已经运行的审查实验。本文也不宣称发现了沙箱漏洞。1把只读参数和写文件调用放在一起看插件的codex.mjs在构造新线程参数时包含approvalPolicy:options.approvalPolicy??never,sandbox:options.sandbox??read-only,这两个字段被作为 App Server 线程参数使用。普通审查路径显式指定read-only反方审查路径也传入相同设置一般任务则由request.write决定选择workspace-write还是read-only。因此判断任务权限时应追到具体调用处不能只看函数默认值。23与此同时state.mjs中的writeJobFile做的是ensureStateDir(cwd);constjobFileresolveJobFile(cwd,jobId);fs.writeFileSync(jobFile,${JSON.stringify(payload,null,2)}\n,utf8);这里执行的是插件的 Node.js 文件操作不是让 Codex 模型通过受控工具修改文件。codex-companion.mjs的后台排队路径会调用它保存任务记录。源码还会更新state.json。43关键差异不在“JSON 算不算写入”而在权限设置的对象传给 Codex 线程的sandbox字段没有把调用它的所有宿主代码自动变成同一沙箱内的操作。插件宿主仍受启动它的操作系统用户、父进程环境和外层限制约束本文没有检查某台机器上的实际隔离配置不能断言宿主拥有任意路径权限。这也给排障提供了顺序先确认写入者和路径再讨论是否违反了那一层的约束。任务记录按预期更新与模型是否改了被审查文件是两个不同问题。never表示不弹审批不是所有动作都获准同一段参数里approvalPolicy和sandbox是两个独立字段。前者管理审批交互后者指定任务的沙箱模式。官方文档将read-only配合never列为非交互只读组合不发起审批询问仍在只读沙箱边界内工作。因此不能把源码里的never翻译成“任何命令都能直接执行”。5还有一个容易遗漏的实现细节固定提交的app-server.mjs对服务端主动请求的默认处理是返回-32601的Unsupported server request错误。它没有在这个处理函数中实现一个让用户点击同意的审批界面。6据此可以得到一个有限但实用的判断不能仅凭自己在交互式 Codex 中见过审批弹窗就推断这个插件客户端也会完成同样的交互。具体某个动作会被拒绝、返回错误还是不触发请求仍由实际 Codex 版本、策略与调用路径决定本文没有运行这些分支。如果审查因为权限不足而失败先保留原始错误再确认是否确实需要那项操作。直接把任务改成全访问会同时改变待验证的条件无法证明原来只是缺少一个弹窗。本地登录和配置从哪里进入这条链官方 README 说明插件使用环境中安装的全局codex二进制复用本地 Codex CLI 的认证并读取相应配置项目级配置需在项目受信任时加载。7直接连接路径提供了更具体的证据SpawnedCodexAppServerClient启动codex app-server时传入工作目录环境使用this.options.env ?? process.env。这是一个继承进程环境的启动点不是插件建立了另一套独立账号。6但“复用配置”仍不能代替检查最终请求参数。前面已经看到插件会为线程提供approvalPolicy和sandbox检查用户配置中的某个默认值只能完成排查的一部分。对团队来说更实际的核对对象是使用哪个 Codex 二进制和版本、以哪个工作目录启动、由哪个本地用户持有登录状态以及具体命令最终请求哪种沙箱。核对时记录账号归属和配置来源即可不要把认证文件内容复制到问题报告。如果运行环境存在组织强制策略实际允许值还要受这些约束影响。本稿只说明插件怎样提出请求不把源码中的请求值当作服务器已经授予的全部有效权限。MCP 要沿工具调用再核对一次假设团队给 Codex 配置了工单 MCP其中既有get_ticket也有close_ticket。这是用于说明边界的假想工具组合不代表本文已连接或测试某个服务。审查线程请求read-only并不足以证明远端工单状态不会变化。判断close_ticket能不能执行需要继续检查该工具是否对当前会话开放、工具审批策略如何设置以及服务端凭据真正允许什么操作。官方 MCP 文档提供了enabled_tools、disabled_tools和逐工具审批配置禁用清单在启用清单之后应用。writes模式依据工具是否标注为只读决定是否询问。8这些是不同性质的控制。工具清单决定暴露什么审批决定怎样批准一次调用服务端权限决定凭据能够完成什么。工具描述里写着“只读”不能代替对服务端权限的核对同样仅从本地源码也不能推断某个远端调用必然成功。若当前任务只需要读工单一个容易核查的起点是仅开放必要读工具并使用服务端本就不能改工单的凭据。需要写工具时应单独验证其审批与拒绝路径。这里提出的是配置与验收方法不是声称某段示例配置已经在所有版本的插件里通过。用一份排查记录把权限落到具体操作前面的差异可以用于处理开头的问题。下面是待填写的排查记录观察栏必须来自实际机器不要把“预期”当作已经发生的结果。要核对的对象应记录的证据这项证据回答什么插件与 Codex 版本插件提交、Codex 版本、调用命令是否在比较同一个实现审查线程实际线程参数或可追溯调用路径、错误输出请求了什么约束哪里未被满足仓库文件变化明确目标文件的前后哈希、差异与可获得的执行记录哪些文件变了能否归因于具体操作插件任务文件已确认的状态目录、文件更新时间与任务 ID是否属于宿主保存任务的路径外部工具当前会话工具清单、审批设置、服务端最小权限说明远端副作用由什么控制state.mjs将状态根目录放在CLAUDE_PLUGIN_DATA下的state中缺少该变量时回退到系统临时目录下的codex-companion。子目录还结合工作区名称与规范化路径的哈希因而不能简单把所有任务文件都归到一个固定仓库内目录。4这份记录有两个用途。排障时它帮助区分“任务没有修改代码”“插件更新了本地状态”“外部系统发生了变化”避免用一个“只读”标签解释所有现象。验收时它迫使团队明确自己要限制的是哪项操作再选择对应的控制。需要进一步验证时应在无敏感数据的隔离环境里做小范围测试记录环境版本准备一个可丢弃目标文件观察预期禁止的修改是否被拒绝外部工具则使用测试服务端与无写权限的测试凭据独立验证拒绝结果。本文没有执行这些测试也没有把它们计为通过。文件最终没变不能单独证明某次写入被沙箱阻止也可能根本没有尝试。工单没关闭也不能单独证明服务端权限有效也可能调用没有到达服务端。记录必须包含与判断相匹配的执行证据缺失时就保留为待验证。接入团队前先决定需要哪一种能力如果需求只是给一小段差异提供第二意见先采用不连接高权限外部工具的审查环境更容易解释行为如果确实需要修改仓库或操作外部系统再分别开启并验证相关能力。插件宿主自身能够执行文件操作意味着来源审查和本地运行身份依然重要。把 Codex 线程设为只读不能代替对插件代码的信任判断把插件安装自官方仓库也不能代替对本地配置和外部服务凭据的权限核对。这是由调用关系得出的工程判断不是对插件安全性的全面评估。这篇最终要留下的不是“能不能放心用”的笼统答案而是一个可以逐项查证的结论只读审查约束了特定任务整个接入链还包括保存任务的宿主、已有登录配置以及可能被调用的外部工具。发生文件变化或权限错误时把操作归到正确的执行者才能找到真正需要调整的边界。本文完成公开源码核对未运行真实审查任务、沙箱越权测试或 MCP 副作用测试不给出运行时安全通过结论。官方固定提交。本地证据包含源文件 SHA固定的是插件实现不是读者机器上的 Codex 版本。 ↩︎线程参数与原生审查调用buildThreadParams、buildResumeParams、runAppServerReview。 ↩︎反方审查、一般任务与后台排队。 ↩︎ ↩︎状态目录及写入函数resolveStateDir、updateState、writeJobFile。 ↩︎ ↩︎官方审批与安全说明2026-09-20 核对动态文档部署前按安装版本复核。 ↩︎App Server 客户端handleServerRequest、SpawnedCodexAppServerClient.initialize。 ↩︎ ↩︎官方 READMECommon configurations、FAQ。 ↩︎官方 MCP 配置说明2026-09-20 核对工具可见性、审批和服务端权限须分别验证。 ↩︎
