AI工具

Codex Windows 沙箱与 TEMP 目录 ACL 故障排查记录

发布于 2026-07-18 #PowerShell#Windows沙箱#Codex桌面版#ACL#TEMP目录#故障排查

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 错误码 5 表示访问被拒绝。Codex 在刷新沙箱配置时,需要给临时目录添加写入 ACL ,但 Windows 拒绝了这次权限修改。

2.3 确认 TEMP / TMP 来源

可用以下 PowerShell 命令分别检查进程、用户和系统级配置:

1
2
3
4
5
6
[Environment]::GetEnvironmentVariable('TEMP', 'Process')
[Environment]::GetEnvironmentVariable('TMP', 'Process')
[Environment]::GetEnvironmentVariable('TEMP', 'User')
[Environment]::GetEnvironmentVariable('TMP', 'User')
[Environment]::GetEnvironmentVariable('TEMP', 'Machine')
[Environment]::GetEnvironmentVariable('TMP', 'Machine')

本机当时的关键结果:

Process TEMP = D:\UserTemp
User TEMP    = D:\UserTemp

2.4 检查临时目录 ACL

1
2
Get-Acl -LiteralPath 'D:\UserTemp' |
Format-List Owner, AreAccessRulesProtected, Access

检查发现:

  • 目录所有者是 BUILTIN\Administrators
  • 当前用户不是所有者;
  • 当前用户主要通过 Authenticated Users 获得普通修改权限;
  • 普通“可以创建文件”不代表“可以修改目录 ACL”。
Authenticated Users权限

因此,日常程序可以往 D:\UserTemp 写临时文件,但 Codex 的沙箱初始化程序无法给该目录追加沙箱用户和 capability SID 所需的 ACE


3. 根因

根因是下面几个条件叠加:

  1. Codex 使用 Windows elevated 沙箱,并在启动命令前刷新可写根目录 ACL;
  2. 用户级 %TEMP% 指向 D:\UserTemp
  3. Codex 自动把 %TEMP% 识别为沙箱可写根目录;
  4. D:\UserTemp 的所有权和 ACL 不允许当前初始化流程追加所需 ACE;
  5. ACL 更新失败后,整个 sandbox setup refresh 被判定失败,导致普通命令也无法启动。

4. 第一阶段:临时绕过 TEMP ACL

C:\Users\<用户名>\.codex\config.toml 中保留 workspace-write,并让沙箱不要自动加入环境变量指定的临时目录:

1
2
3
4
5
sandbox_mode = "workspace-write"

[sandbox_workspace_write]
network_access = true
exclude_tmpdir_env_var = true

重启 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 刷新沙箱成功后,新增了 CodexSandboxUsersModify 权限;
  • 同时新增了一个专用 SID 的 Modify 权限。

这解释了为什么修复后能够成功启动:初始化程序终于可以修改 D:\UserTemp 的 DACL ,并写入沙箱身份所需的 ACE。

5.1 权限范围说明

BUILTIN\Users 是一个用户组,它的范围不只包含当前用户。给它“完全控制”确实解决了本机故障,但权限范围比实际所需更宽。

更收敛的做法是只让当前用户具备修改 D:\UserTemp 根目录权限的能力,保留 Authenticated Users 的普通“修改”权限,让 Codex 再自行添加 CodexSandboxUsers 和 capability SID 所需的 ACE。


5.2 第三阶段:收紧过宽权限

确认故障已经解除后,又对 ACL 做了一次收紧:

  1. 显式添加当前个人账号 LAPTOP-机器编号\用户名,授予 FullControl
  2. 移除 BUILTIN\Users 原先过宽的 FullControl
  3. 保留 BUILTIN\UsersReadAndExecute
  4. 保留 Authenticated UsersCodexSandboxUsers 和沙箱专用 SID 已有的权限。

收紧后的实际 ACL 状态:

  • 所有者:BUILTIN\Administrators
  • LAPTOP-机器编号\用户名:显式 FullControl
  • LAPTOP-机器编号\CodexSandboxUsers:显式 Modify
  • Authenticated Users:继承的 Modify
  • BUILTIN\UsersReadAndExecute,不再拥有写入或完全控制权限。

当前还存在一条显式的 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,可以:

  1. D: 盘新建一个仅供当前用户使用的目录,例如 D:\Temp\<用户名>
  2. 确认目录所有者是当前用户,并且当前用户拥有完全控制权限;
  3. 把用户级 TEMPTMP 都指向该目录;
  4. 重启 Codex;
  5. 重新验证沙箱启动、仓库写入和 TEMP 写入;
  6. 验证无误后,再移除旧目录中过宽的 BUILTIN\Users FullControl

不建议直接对正在使用的 D:\UserTemp 执行递归 takeown、ACL 重置或大范围权限覆盖;这可能影响其他程序,并且不利于回滚。

8. 排查时的安全注意事项

  • 优先执行只读检查,再决定是否修改权限;
  • 不要为了绕过问题长期使用 Full Access;
  • 修复后应同时测试“命令能启动”和“仓库能写入”,只验证其中一个不够。

9. 配置“项目内自动执行,项目外写入时询问”

本次希望得到的权限效果是:

  • Codex 可以直接读取所有本地文件;
  • Codex 可以直接修改当前项目目录;
  • 修改项目目录以外的文件时,需要弹窗询问;
  • 项目内的普通构建、检查和编辑操作不重复询问。

对应的全局 config.toml 配置如下:

1
2
3
4
5
6
7
8
9
10
# 在沙箱边界内自动执行,越过边界时询问
approval_policy = "on-request"
approvals_reviewer = "user"

# 当前工作区可写,其他路径默认只读
sandbox_mode = "workspace-write"

# 将当前项目标记为可信项目
[projects.'d:\blog\blog-source']
trust_level = "trusted"

这组配置中,sandbox_mode 决定 Codex 技术上可以访问和修改哪里approval_policy 决定 Codex 什么时候必须停下来询问workspace-write + on-request 就是适合可信代码仓库的 Auto 组合:工作区内自动执行,需要写入工作区外时再申请批准。

如果还希望项目内的安装依赖、开发服务等命令可以联网,可另外开启:

1
2
[sandbox_workspace_write]
network_access = true

网络权限与本地文件读取权限是两套独立控制。只想读取项目外的本地文件,并不要求开启网络。

下面两种配置不符合上述目标:

  • approval_policy = "never":不会显示审批弹窗,但工作区外写入通常会直接失败,而不是询问;
  • sandbox_mode = "danger-full-access":取消了文件系统边界,项目外也可以直接修改,权限过宽。

Codex 桌面版输入框下方的权限菜单也会影响当前任务。应选择 Ask for approval 或对应的 Auto 档位,而不是 Read onlyFull access。修改 config.toml 后,完全退出并重启 Codex,再新建任务验证最稳妥。

官方说明可参考:Agent approvals & security

10. PowerShell 只读命令为什么仍可能弹窗

执行一条 shell 命令时,Codex 本来就需要短暂启动 PowerShell;npm run build 等命令还会同步启动 Node.js 子进程。这不等于启动后台服务,也不是触发审批的直接原因。真正的 Start-ProcessStop-Process 会改变系统进程状态,应继续按具体操作决定是否批准。

更常见的问题是:审批器需要先判断一条命令是否安全。如果命令是简单、可识别的只读形式,例如:

1
2
3
4
Get-Content README.md
rg "关键词" source
git status
git diff

就可以通过命令前缀规则明确放行。但如果把读取操作组合成一大段 PowerShell 脚本,使用变量、foreach、条件分支、重定向或复杂管道,审批器有时只能看到完整的 powershell.exe -Command "...",无法可靠证明其中没有写入或进程操作,因此仍可能弹窗。

个人命令规则默认保存在:

1
C:\Users\<用户名>\.codex\rules\default.rules

可以为常用的只读命令添加规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
prefix_rule(pattern=["Get-Content"], decision="allow")
prefix_rule(pattern=["Get-ChildItem"], decision="allow")
prefix_rule(pattern=["Get-Item"], decision="allow")
prefix_rule(pattern=["Test-Path"], decision="allow")
prefix_rule(pattern=["Select-String"], decision="allow")
prefix_rule(pattern=["Resolve-Path"], decision="allow")
prefix_rule(pattern=["Get-Process"], decision="allow")
prefix_rule(pattern=["Get-CimInstance"], decision="allow")
prefix_rule(pattern=["Get-Counter"], decision="allow")
prefix_rule(pattern=["rg"], decision="allow")
prefix_rule(pattern=["git", "status"], decision="allow")
prefix_rule(pattern=["git", "diff"], decision="allow")
prefix_rule(pattern=["git", "show"], decision="allow")
prefix_rule(pattern=["git", "log"], decision="allow")
prefix_rule(pattern=["git", "blame"], decision="allow")
prefix_rule(pattern=["git", "ls-files"], decision="allow")

prefix_rule 按命令及参数前缀匹配。decision="allow" 表示匹配后直接运行,不再弹窗;如果多条规则同时匹配,则以限制最严格的一条为准。

不要为了消除弹窗而加入下面这种规则:

1
2
3
4
prefix_rule(
pattern=["powershell.exe", "-Command"],
decision="allow",
)

它会放行所有通过 powershell.exe -Command 执行的脚本,不仅包括读取,也可能包括写文件、删除文件和进程控制,范围过大。

添加规则后保存文件没有问题,但当前任务不保证动态重新载入。完全退出并重启 Codex 后,规则会在启动时重新扫描并生效。长期使用时,仍应优先让只读检查保持为简单、独立的命令,而不是依赖一条过宽的 PowerShell 放行规则。

官方规则格式可参考:Rules

评论
分享

评论