1. 故障现象
Codex 桌面版 使用 workspace-write 沙箱时,连简单的 PowerShell 只读命令都无法启动,统一返回:
1 | windows sandbox: helper_unknown_error: setup refresh had errors |
config.toml中切换到 sandbox = “unelevated” 后,又遇到跨盘、多个可写根目录无法正确处理的问题:
1 | windows unelevated restricted-token sandbox cannot enforce split writable root sets directly |
项目位于 D: 盘,用户级 %TEMP% 也被配置到了 D:\UserTemp。
2. 排查步骤
2.1 先区分“项目目录问题”与“沙箱初始化问题”
在用户批准后,于沙箱外执行只读命令,确认:
- PowerShell 可以正常启动;
D:\项目路径可以访问;- 项目 HTML 文件存在;
- 当前进程的
TEMP仍是D:\UserTemp。
这说明项目目录和 PowerShell 本身正常,故障发生在命令进入沙箱之前。
2.2 查看沙箱日志
主要日志位于:
1 | C:\Users\<用户名>\.codex\.sandbox\sandbox.<日期>.log |
日志中的关键内容是:
granting write ACE to D:\UserTemp for sandbox group and capability SID
write ACE grant failed on D:\UserTemp: SetNamedSecurityInfoW failed: 5
setup refresh completed with errors
Windows 错误码 这三个术语用于描述 Windows 的访问权限控制,它们是逐层包含的关系: ACL ACL(Access Control List,访问控制列表) 是权限控制条目列表的统称。Windows 中常见的 ACL 有两类: 日常所说的“修改文件或目录 ACL”,通常是指修改它的 DACL。 DACL DACL(Discretionary Access Control List,自主访问控制列表) 决定哪些用户、用户组或进程可以访问文件、目录、注册表项等 Windows 对象,以及可以执行读取、写入、修改、删除等哪些操作。 Windows 收到访问请求时,会根据对象 DACL 中的规则判断允许还是拒绝。 ACE ACE(Access Control Entry,访问控制项) 是 ACL 中的一条具体规则,通常包含: 本文日志中的“添加写入 ACE”是指:Codex 尝试在 TEMP 目录的 DACL 中加入一条规则,允许沙箱组或 Capability SID 写入该目录。Windows 拒绝修改 DACL 后,Codex 无法完成沙箱初始化。5 表示访问被拒绝。Codex 在刷新沙箱配置时,需要给临时目录添加写入 ACL 安全描述符(Security Descriptor)
└─ DACL:决定谁可以访问、可以执行哪些操作
├─ ACE:允许用户 A 读取
├─ ACE:允许沙箱组写入
└─ ACE:拒绝用户 B 删除
2.3 确认 TEMP / TMP 来源
可用以下 PowerShell 命令分别检查进程、用户和系统级配置:
1 | [Environment]::GetEnvironmentVariable('TEMP', 'Process') |
本机当时的关键结果:
Process TEMP = D:\UserTemp
User TEMP = D:\UserTemp
2.4 检查临时目录 ACL
1 | Get-Acl -LiteralPath 'D:\UserTemp' | |
检查发现:
- 目录所有者是
BUILTIN\Administrators; - 当前用户不是所有者;
- 当前用户主要通过
Authenticated Users获得普通修改权限; - 普通“可以创建文件”不代表“可以修改目录 ACL”。
因此,日常程序可以往 这三个术语用于描述 Windows 的访问权限控制,它们是逐层包含的关系: ACL ACL(Access Control List,访问控制列表) 是权限控制条目列表的统称。Windows 中常见的 ACL 有两类: 日常所说的“修改文件或目录 ACL”,通常是指修改它的 DACL。 DACL DACL(Discretionary Access Control List,自主访问控制列表) 决定哪些用户、用户组或进程可以访问文件、目录、注册表项等 Windows 对象,以及可以执行读取、写入、修改、删除等哪些操作。 Windows 收到访问请求时,会根据对象 DACL 中的规则判断允许还是拒绝。 ACE ACE(Access Control Entry,访问控制项) 是 ACL 中的一条具体规则,通常包含: 本文日志中的“添加写入 ACE”是指:Codex 尝试在 TEMP 目录的 DACL 中加入一条规则,允许沙箱组或 Capability SID 写入该目录。Windows 拒绝修改 DACL 后,Codex 无法完成沙箱初始化。D:\UserTemp 写临时文件,但 Codex 的沙箱初始化程序无法给该目录追加沙箱用户和 capability SID 所需的 ACE 安全描述符(Security Descriptor)
└─ DACL:决定谁可以访问、可以执行哪些操作
├─ ACE:允许用户 A 读取
├─ ACE:允许沙箱组写入
└─ ACE:拒绝用户 B 删除
3. 根因
根因是下面几个条件叠加:
- Codex 使用 Windows elevated 沙箱,并在启动命令前刷新可写根目录 ACL;
- 用户级
%TEMP%指向D:\UserTemp; - Codex 自动把
%TEMP%识别为沙箱可写根目录; D:\UserTemp的所有权和 ACL 不允许当前初始化流程追加所需 ACE;- ACL 更新失败后,整个 sandbox setup refresh 被判定失败,导致普通命令也无法启动。
4. 第一阶段:临时绕过 TEMP ACL
在 C:\Users\<用户名>\.codex\config.toml 中保留 workspace-write,并让沙箱不要自动加入环境变量指定的临时目录:
1 | sandbox_mode = "workspace-write" |
重启 Codex 后重新验证:
- 普通沙箱命令成功启动;
D:\项目路径可读写;- 在仓库内创建并删除探针文件成功;
%TEMP%仍然显示为D:\UserTemp。
这里需要特别注意:exclude_tmpdir_env_var = true 不会修改或迁移 %TEMP%。它只是阻止 Codex 把该目录自动加入沙箱可写根目录,因此也不再尝试修改 D:\UserTemp 的 ACL。
4.1 临时绕过的影响
日常编辑仓库文件、运行普通命令基本不受影响。不过,沙箱内某些强依赖系统 %TEMP% 的程序可能无法写入 D:\UserTemp,例如个别安装器、压缩工具、编译器或脚本。
遇到这种情况时,可以只为那一条命令指定一个已允许写入的临时目录,例如 C:\tmp,不必修改全局系统配置。
5. 第二阶段:修复目录 ACL
为了恢复 Codex 对 %TEMP% 的自动支持,随后在 Windows 的“安全”设置中调整了 D:\UserTemp 权限,为BUILTIN\Users用户组勾选了 完全控制权限。并删除了 config.toml 中的:
1 | exclude_tmpdir_env_var = true |
修复后的实际 ACL 检查结果显示:
- 目录所有者仍是
BUILTIN\Administrators; BUILTIN\Users获得了一条显式的FullControl;- Codex 刷新沙箱成功后,新增了
CodexSandboxUsers的Modify权限; - 同时新增了一个专用 SID 的
Modify权限。
这解释了为什么修复后能够成功启动:初始化程序终于可以修改 这三个术语用于描述 Windows 的访问权限控制,它们是逐层包含的关系: ACL ACL(Access Control List,访问控制列表) 是权限控制条目列表的统称。Windows 中常见的 ACL 有两类: 日常所说的“修改文件或目录 ACL”,通常是指修改它的 DACL。 DACL DACL(Discretionary Access Control List,自主访问控制列表) 决定哪些用户、用户组或进程可以访问文件、目录、注册表项等 Windows 对象,以及可以执行读取、写入、修改、删除等哪些操作。 Windows 收到访问请求时,会根据对象 DACL 中的规则判断允许还是拒绝。 ACE ACE(Access Control Entry,访问控制项) 是 ACL 中的一条具体规则,通常包含: 本文日志中的“添加写入 ACE”是指:Codex 尝试在 TEMP 目录的 DACL 中加入一条规则,允许沙箱组或 Capability SID 写入该目录。Windows 拒绝修改 DACL 后,Codex 无法完成沙箱初始化。D:\UserTemp 的 DACL 安全描述符(Security Descriptor)
└─ DACL:决定谁可以访问、可以执行哪些操作
├─ ACE:允许用户 A 读取
├─ ACE:允许沙箱组写入
└─ ACE:拒绝用户 B 删除
5.1 权限范围说明
BUILTIN\Users 是一个用户组,它的范围不只包含当前用户。给它“完全控制”确实解决了本机故障,但权限范围比实际所需更宽。
更收敛的做法是只让当前用户具备修改 D:\UserTemp 根目录权限的能力,保留 Authenticated Users 的普通“修改”权限,让 Codex 再自行添加 CodexSandboxUsers 和 capability SID 所需的 ACE。
5.2 第三阶段:收紧过宽权限
确认故障已经解除后,又对 ACL 做了一次收紧:
- 显式添加当前个人账号
LAPTOP-机器编号\用户名,授予FullControl; - 移除
BUILTIN\Users原先过宽的FullControl; - 保留
BUILTIN\Users的ReadAndExecute; - 保留
Authenticated Users、CodexSandboxUsers和沙箱专用 SID 已有的权限。
收紧后的实际 ACL 状态:
- 所有者:
BUILTIN\Administrators; LAPTOP-机器编号\用户名:显式FullControl;LAPTOP-机器编号\CodexSandboxUsers:显式Modify;Authenticated Users:继承的Modify;BUILTIN\Users:ReadAndExecute,不再拥有写入或完全控制权限。
当前还存在一条显式的 BUILTIN\Users → ReadAndExecute,与继承得到的读取和执行权限重复。这不会扩大写入权限,不影响安全或沙箱运行;如果希望完全恢复原始 ACL 形态,可以只删除这条“非继承”的重复项。
6. 最终复测结果
恢复 %TEMP% 自动加入沙箱后进行了一次完整复测;收紧 BUILTIN\Users 权限后,又重复执行了 TEMP 写入测试。两次测试均成功:
- 普通 PowerShell 命令在沙箱中成功启动;
- 当前进程仍显示
TEMP=D:\UserTemp; D:\MoonTide可正常读取;- 在仓库内创建并删除探针文件成功;
- 在
D:\UserTemp直接创建临时探针文件成功; - 临时探针文件删除成功。
测试输出:
TEMP_WRITE=True
TEMP_CLEANED=True
这次验证同时覆盖了“沙箱初始化”“仓库写入”和“真实 TEMP 写入”,可以确认原始故障已经解决。
7. 更收敛的长期方案
如果之后希望收紧当前 ACL,可以:
- 在
D:盘新建一个仅供当前用户使用的目录,例如D:\Temp\<用户名>; - 确认目录所有者是当前用户,并且当前用户拥有完全控制权限;
- 把用户级
TEMP和TMP都指向该目录; - 重启 Codex;
- 重新验证沙箱启动、仓库写入和 TEMP 写入;
- 验证无误后,再移除旧目录中过宽的
BUILTIN\Users FullControl。
不建议直接对正在使用的 D:\UserTemp 执行递归 takeown、ACL 重置或大范围权限覆盖;这可能影响其他程序,并且不利于回滚。
8. 排查时的安全注意事项
- 优先执行只读检查,再决定是否修改权限;
- 不要为了绕过问题长期使用 Full Access;
- 修复后应同时测试“命令能启动”和“仓库能写入”,只验证其中一个不够。
9. 配置“项目内自动执行,项目外写入时询问”
本次希望得到的权限效果是:
- Codex 可以直接读取所有本地文件;
- Codex 可以直接修改当前项目目录;
- 修改项目目录以外的文件时,需要弹窗询问;
- 项目内的普通构建、检查和编辑操作不重复询问。
对应的全局 config.toml 配置如下:
1 | # 在沙箱边界内自动执行,越过边界时询问 |
这组配置中,sandbox_mode 决定 Codex 技术上可以访问和修改哪里,approval_policy 决定 Codex 什么时候必须停下来询问。workspace-write + on-request 就是适合可信代码仓库的 Auto 组合:工作区内自动执行,需要写入工作区外时再申请批准。
如果还希望项目内的安装依赖、开发服务等命令可以联网,可另外开启:
1 | [sandbox_workspace_write] |
网络权限与本地文件读取权限是两套独立控制。只想读取项目外的本地文件,并不要求开启网络。
下面两种配置不符合上述目标:
approval_policy = "never":不会显示审批弹窗,但工作区外写入通常会直接失败,而不是询问;sandbox_mode = "danger-full-access":取消了文件系统边界,项目外也可以直接修改,权限过宽。
Codex 桌面版输入框下方的权限菜单也会影响当前任务。应选择 Ask for approval 或对应的 Auto 档位,而不是 Read only 或 Full access。修改 config.toml 后,完全退出并重启 Codex,再新建任务验证最稳妥。
官方说明可参考:Agent approvals & security。
10. PowerShell 只读命令为什么仍可能弹窗
执行一条 shell 命令时,Codex 本来就需要短暂启动 PowerShell;npm run build 等命令还会同步启动 Node.js 子进程。这不等于启动后台服务,也不是触发审批的直接原因。真正的 Start-Process、Stop-Process 会改变系统进程状态,应继续按具体操作决定是否批准。
更常见的问题是:审批器需要先判断一条命令是否安全。如果命令是简单、可识别的只读形式,例如:
1 | Get-Content README.md |
就可以通过命令前缀规则明确放行。但如果把读取操作组合成一大段 PowerShell 脚本,使用变量、foreach、条件分支、重定向或复杂管道,审批器有时只能看到完整的 powershell.exe -Command "...",无法可靠证明其中没有写入或进程操作,因此仍可能弹窗。
个人命令规则默认保存在:
1 | C:\Users\<用户名>\.codex\rules\default.rules |
可以为常用的只读命令添加规则:
1 | prefix_rule(pattern=["Get-Content"], decision="allow") |
prefix_rule 按命令及参数前缀匹配。decision="allow" 表示匹配后直接运行,不再弹窗;如果多条规则同时匹配,则以限制最严格的一条为准。
不要为了消除弹窗而加入下面这种规则:
1 | prefix_rule( |
它会放行所有通过 powershell.exe -Command 执行的脚本,不仅包括读取,也可能包括写文件、删除文件和进程控制,范围过大。
添加规则后保存文件没有问题,但当前任务不保证动态重新载入。完全退出并重启 Codex 后,规则会在启动时重新扫描并生效。长期使用时,仍应优先让只读检查保持为简单、独立的命令,而不是依赖一条过宽的 PowerShell 放行规则。
官方规则格式可参考:Rules。
评论