OpenAI Codex · 代码模式

Windows:受限令牌、防火墙过滤器与两个专用系统用户

没有 seatbelt,也没有 bubblewrap。Windows 上的沙箱是三道关卡叠起来的:令牌管写,专用账户管你是谁,WFP 按这个身份滤网。AppContainer 对不上这份权限模型,整套还默认关着。

课程目标读完能说清三件事。Windows 上为什么只能按身份锁资源。受限令牌那三个标志为什么管得住写、管不住读和网络。为什么要两个专用系统用户,以及 AppContainer 为什么被放下。
先玩一遍 · 一个请求穿过三道关
换动作、换档位,看请求在哪一关停下
动作
档位
先看 Elevated 怎么把读挡在身份关。再降到 RestrictedToken,同一条读取会穿过去。
这次请求
读 C:\Users\you\.ssh\id_rsa
当前身份
CodexSandboxOffline
关卡 1 · 受限令牌待命
三个标志裁令牌。写必须过交叉检查,读在这一档仍跟当前用户走。
关卡 2 · 专用系统用户待命
Elevated 才登录 CodexSandboxOffline 或 Online,按账户 ACL 挖空 .ssh 这类目录。
关卡 3 · 防火墙过滤器待命
只给离线账户装持久 WFP。ICMP、DNS、SMB 被内核滤掉,普通出站给出站防火墙。
点播放,看这个请求依次走过三道关。
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. Windows 开关关着,平台沙箱返回空,后面不会包包装器manager.rs L62
  2. 需要沙箱时,用自己的 exe 加隐藏参数当包装器wrapper.rs L20
  3. 三个标志裁出受限令牌:削特权、LUA、写必须过交叉检查token.rs L480
  4. RestrictedToken 这一档,读检查不看 capability SIDwindows.rs L109
  5. Elevated 先用专用账户登录,再从那个令牌往下裁runner_client.rs L348
  6. 两个账户名写死:Offline 与 Onlinesetup.rs L50
  7. 网络策略未开或走代理时,选 Offline 身份setup.rs L706
  8. 只给离线账户装持久 WFP 过滤器wfp.rs L75
  9. 用户配置根默认挖空 .ssh 等目录setup.rs L56
  10. 没有后端时,只读档案加 Never,未匹配命令必须 Forbiddenexec_policy_windows_tests.rs L113
点播放,看这个请求依次走过三道关。
令牌档只锁写RestrictedToken 从当前用户裁令牌。读工作区外的文件、往外发网络,这两条都会穿过。要拦它们,必须换 Elevated。
身份关才有挖空.ssh 出现在用户配置排除列表里。只有登录了专用账户,这份 ACL 才会生效。
网络挂在 SID 上WFP 只装给离线账户。令牌档没有这两个账户,第三关根本不存在。
教学示意:路径与账户名为课程化设定。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 没有进程视图,就按身份锁资源
它解决什么问题

同事在 Mac 上跑 Codex,agent 去碰用户目录里的 SSH 私钥路径,立刻被 Seatbelt 拦住,错误写着 Operation not permitted。同一条读取放到 Windows 上,报错变成 Access is denied。再换一台没开 Windows 沙箱的机器,这条命令可能根本走不到 ACL,execpolicy 先把它判成 Forbidden 或 Prompt。

三台机器、同一份意图、三种失败。模型看到的下一句话不一样,下一步也会跟着变。如果 Windows 上假装有和 Unix 一样的默认沙箱,模型会按自己被关着去规划,实际命令裸跑。

思路是什么

macOS 限制这个进程能对哪些路径做哪些操作。Linux 限制这个进程能看见什么、能调哪些系统调用。Windows 没有 namespace,也没有 seccomp。Codex 换成另一套问题:这个身份能碰哪些资源。

令牌决定你是谁。RestrictedToken 档从当前用户令牌裁,Elevated 档先用专用账户登录,再从那个令牌裁。两边最后都进同一组标志:DISABLE_MAX_PRIVILEGELUA_TOKENWRITE_RESTRICTED。特权被削薄,写必须同时通过普通 ACL 和 restricting SID 的交叉检查。

读在 RestrictedToken 这一档仍然跟当前用户走。源码自己写了:WRITE_RESTRICTED 令牌上的 capability SID deny-read ACE 不参与读检查,所以读限制必须走 Elevated,裸跑会被直接拒绝。出处:codex-rs/windows-sandbox-rs/src/token.rs 第 480 行;codex-rs/sandboxing/src/windows.rs 第 109 至 117 行

macOS 锁进程能做什么 SBPL 文本 file-read 规则拒绝 Operation not permitted Linux 锁进程能看见什么 bubblewrap 视图 路径不在挂载里 No such file or directory Windows 锁这个身份能碰什么 令牌加 ACL 身份没有权限 Access is denied
教学化结构图:同一条读取,三套隔离锁在不同层,模型看到的失败语义也不一样。
为什么长期成立

主体能做什么,和客体能被谁碰,是两套隔离原语。换语言重写,Windows 上仍然没有 bwrap 可以调用。该问的还是:你有没有一份身份,这份身份被允许碰什么。

包装方式也是同一类自我调用。Windows 用自己的 exe 加上隐藏参数 --run-as-windows-sandbox,跟 Linux 改 arg0 是同一思路。出处:codex-rs/windows-sandbox-rs/src/wrapper.rs 第 20 行

思路二 · 一个身份只能挂一套网络策略
它解决什么问题

WFP 是 Windows Filtering Platform,一套内核里的包过滤框架。它和防火墙规则都按 SID 匹配。一个 SID 只能对应一套网络策略。同一身份没法同时表示完全没网,也没法同时表示可以走代理或出网。

思路是什么

所以用两个本地用户当两个网络身份:CodexSandboxOfflineCodexSandboxOnlinefrom_permissions 在强制走代理,或网络策略未启用时,选 Offline。离线身份装出站阻断、代理端口白名单,再加 12 条 WFP,覆盖 ICMP、DNS 53、DNS-over-TLS 853 和 SMB。在线身份不装这套阻断。

Unix 的网络隔离是给进程换一张网卡视图。WFP 是进程还在主机网络栈上,只是这个 SID 发出的特定流量被挡住。进程仍然能看见网卡、解析失败、连别的端口。

过滤器用稳定 GUID 标识,带着持久标志,重启后还在。卸载和崩溃都不会自动拆掉它们。仓库里检索不到删除用户或拆除 WFP 的函数。WFP 失败被写成非致命,防火墙失败会让 setup 整段失败。出处:codex-rs/windows-sandbox-rs/src/setup.rs 第 50 至 51 行、第 706 至 714 行;codex-rs/windows-sandbox-rs/src/wfp.rs 第 69 至 95 行

一个 SID,两套互相打架的网策 同一个账户 完全没网 可以走代理或出网 过滤器按 SID 匹配,装不上两套 两个账户,两套网策 CodexSandboxOffline 12 条 WFP 加出站阻断 Online 不装这套阻断 出网
教学化结构图:网络策略有两档,就需要两个身份。
为什么长期成立

按身份做访问控制,身份数量必须覆盖策略组合数。网络策略有两档,就需要两个身份。这和用两张门禁卡进两栋楼是同一件事,跟 WFP 这个具体 API 无关。

思路三 · AppContainer 对不上,Elevated 又太贵,所以默认关
它解决什么问题

有人会问为什么不上 AppContainer。它的可读范围默认很窄,任意路径读取要改宿主 DACL。Codex 需要 workspace 可读、平台根可读,再按 deny 列表挖空。受限令牌加 ACL 更贴这份权限模型。在 codex-rs/ 里检索 AppContainer,业务代码零命中。对照仓库 DeepSeek Harness 把同一判断写成了明文:AppContainer 做不了任意路径读取。

Elevated 要把读和网络也管住,就要建账户、弹 UAC、改防火墙和 WFP。企业策略可能禁止本地建用户。把默认设成 Disabled,等于把这道产品摩擦留给第一次启用。

思路是什么

枚举自己把默认值写成关。没写 windows.sandbox、两个 legacy flag 也没有时,就落回 Disabled。legacy flag 已经被标成 Removed。

codex-rs/protocol/src/config_types.rs第 297 至 302 行
pub enum WindowsSandboxLevel {
    #[default]
    Disabled,
    RestrictedToken,
    Elevated,
}
源码快照说明:依据本地仓库 openai/codex,核对文件 codex-rs/protocol/src/config_types.rs,commit 4f39251a01,核对日期 2026-08-22。代码块保留源码原文,#[default] 钉在 Disabled 上。

关掉之后,平台沙箱确实没了。get_platform_sandbox(false) 在 Windows 上返回空,select_initial 把它变成 SandboxType::None。命令仍可能被策略层拦住。Windows 测试锁住了这条:没有后端时,只读档案加 Never 审批,未匹配的 cmd.exe /c dir 必须是 Decision::Forbidden出处:codex-rs/sandboxing/src/manager.rs 第 62 至 72 行、第 301 行;codex-rs/core/src/exec_policy_windows_tests.rs 第 113 至 130 行

AppContainer 默认可读范围很窄 任意路径要改宿主 DACL 对不上这份权限模型 组合拳 令牌锁写 专用账户锁读 WFP 按 SID 滤网 要 UAC,要本地用户 默认 Disabled execpolicy 兜底
教学化结构图:用得上的那套太贵,默认先关,策略层兜住裸跑。
三道关卡,一道管写,一道管你是谁,一道管这个身份能出哪扇网门。
为什么长期成立

隔离深度跟安装预算挂钩。只防模型误改仓库,当前用户受限令牌就够。要防读出密钥再从这台机器往外传,必须再做读限制和按身份的网络过滤。承诺了做不到的墙,模型和用户都会按那道墙做决策。

跨平台能力不对等时,对外说法必须按实际启用的那一层来写。macOS 默认就有 Seatbelt,Windows 默认可能是 Disabled。用户听到有沙箱,会以为三台机器同一道墙。

横向对比 · 同一组标志,三种愿意付的代价

DeepSeek Harness:同一组标志,停在写限制

DSH 的 @deepseek-ai/dsh-sandbox-windows-acl 用同一组 CreateRestrictedToken 标志:DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED。README 写明读、网络、进程可见性不受限,enforcement 标成 partial。它不建专用账户,不装 WFP。

设计笔记把 AppContainer 否掉的原因写死了:AppContainer 令牌没有环境读权限,任意路径读取必须预先授权,对不上 harness 的读模型。mxc 被否是因为系统版本底线太新,任意路径读取还是要改宿主 DACL。Codex Elevated 用两个账户加 WFP 把读和网络身份也做进去,代价是安装摩擦和卸载残留。DSH 换到的是装得上、跑得动,读侧和网络侧留给别的机制。

已核对 DSH token.ts / win32-abi.ts / README / 设计笔记 · 2026-08-22 · DSH · 沙箱:从 seatbelt 到执行世界

Claude 拒绝执行,Grok 把平台标成 unknown

Claude Code 在原生 Windows 上写明没有沙箱。企业策略要求沙箱且禁止无沙箱命令时,PowerShell 直接拒绝执行,文案是 sandboxing is not available on native Windows。它不假装有一层隔离。Grok Build 的沙箱 crate 把平台写成 linux/landlockmacos/seatbelt,其余标 unknown。在 xai-grok-sandbox 里检索 AppContainerRestrictedTokenCreateRestrictedToken,没有对应实现。

答案分成三档。Claude 和 Grok 在 Windows 上不做文件系统沙箱。DSH 做了写限制,装得上,读和网络留给别的机制。Codex 把身份、ACL、WFP、专用账户全做进去,摩擦更大,所以默认关着。

已核对 Claude PowerShellTool.tsx、Grok types.rs · 2026-08-22 · Grok · 五种沙箱 Profile
课堂练习
01

同一条读取,换档之后卡在哪

把演示里的动作留在读工作区外,档位从 Elevated 降到 RestrictedToken。先写下你的预测:哪一关会放行,哪一关会消失,最后是 Access is denied 还是穿过。再点播放核对。

进阶一问:把动作换成发网络。为什么 RestrictedToken 档第三关是跳过,Elevated 档才会亮起 WFP。

Takeaway:Windows 没有 seatbelt 和 bubblewrap,隔离只能按身份锁资源。RestrictedToken 那三个标志管写,不管读和网络。要拦密钥和出网,必须再上专用账户、ACL 和按 SID 装的 WFP。AppContainer 对不上这份可读集合,Elevated 又要 UAC,所以默认关着,execpolicy 在没有后端时改成 Forbidden 或 Prompt。