# OpenAI 给 Codex 在 Windows 造了一个沙箱，过程比想象中曲折

- 来源：meng shao (@shao__meng)
- 发布时间：2026-05-14 13:09
- AIHOT 分数：50
- AIHOT 链接：https://aihot.virxact.com/items/cmp51a1jh0ahnsljxw0w7rzd2
- 原文链接：https://x.com/shao__meng/status/2054791308603265044

## AI 摘要

OpenAI 为在 Windows 上实现 Codex 的“默认安全”体验，从免提权沙箱演进到提权沙箱。Windows 缺乏原生进程级约束，初期方案通过合成 SID 和 Write-Restricted Token 限制文件写入，但网络封锁只能依赖环境变量软拦截，无法强制生效。团队最终放弃免提权约束，转向创建独立本地用户（在线与离线沙箱用户），需一次性管理员权限安装并配置防火墙规则。通过引入 codex-command-runner.exe 作为中介，解决跨用户创建受限令牌进程的权限难题，形成四层架构，在保障安全的同时最小化对主流程的侵入。

## 正文

OpenAI 给 Codex 在 Windows 造了一个沙箱，过程比想象中曲折 …

来自 Codex 团队 David Wiesen 非常有深度的技术博客，推荐阅读！
https://openai.com/index/building-codex-windows-sandbox/

问题的起点：Windows 上的 Codex 没有沙箱
Codex 运行在开发者本地（CLI / IDE 扩展 / App），默认以当前用户身份执行命令--既能读写文件、跑测试、操作 Git，也意味着潜在风险。

macOS 有 Seatbelt，Linux 有 seccomp/bubblewrap，Windows 原生缺乏这种"按进程做强约束"的能力。结果 Windows 用户只能在两个糟糕方案中二选一：
· 每条命令都审批（甚至读操作），打断流畅性；
· 开启 Full Access，放弃所有约束。

团队的目标，是把 Codex 在 macOS/Linux 已有的"默认安全"体验搬到 Windows：只能在工作区内写、默认无网络访问，且全程不需要用户介入。

现成 Windows 方案为什么都不够用？
· AppContainer：是为"功能边界清晰的应用"设计的；Codex 要驱动 shell、Git、Python、构建工具等任意二进制，形状不对
· Windows Sandbox：它是隔离的"另一个桌面"，无法直接作用于用户的真实仓库；且 Windows Home 版根本没有
· Mandatory Integrity Control：把工作区标成 Low，等于让所有 Low 进程都能写入，宿主信任模型被破坏，副作用太大

第一版原型：「免提权沙箱」（Unelevated Sandbox）
设计原则：不弹 UAC、不要求管理员。需要解决两件事：限制文件写入 + 限制网络。

1. 文件写入：靠 SID + Write-Restricted Token 真正落地
· 合成 SID：Windows 允许创建一个不绑定真实用户、却能出现在 ACL 中的身份。Codex 为此造了一个专属的 sandbox-write SID。
· Write-Restricted Token：一种特殊进程令牌，写操作要双重放行--token 的真实用户身份有权限；
token 的"受限 SID 列表"中至少一个 SID 也被授权。

把 sandbox-write SID 通过 ACL 授予：
· 当前工作目录
· config.toml 里配置的 writable_roots

并显式拒绝其写入 .git / .codex / .agents。
→ 这是真正的 OS 级写边界。

2. 网络访问：只能"劝退"，无法强制
Windows Firewall 必须管理员权限，于是只能做环境层面的软封锁：
HTTPS_PROXY / ALL_PROXY / GIT_HTTPS_PROXY = http://127.0.0.1:9
GIT_SSH_COMMAND = cmd /c exit 1
外加在 PATH 前塞 denybin，让假的 ssh/scp 先被解析到。

效果：拦得住行为良好的工具；但凡自己实现网络栈、绕过 PATH、或直接开 socket 的程序--一律失效。仅是 advisory，挡不住对抗性代码。

改版关键：为什么必须接受"需要提权"
要让 Windows Firewall 真正生效，必须按"身份"匹配规则。但：
· 防火墙规则不能匹配 restricted token 中的合成 SID；
· 按 codex.exe 路径匹配，覆盖不到它派生的 Git/Python 等子进程；
· 按用户匹配又会误伤真实用户本人；
· 按端口/地址匹配是错的策略--目标不是封 443，而是封这一棵受限进程树的所有出站流量。

唯一的出路：让沙箱命令以"另一个 Windows 用户"的身份运行。这就必须放弃"免提权"约束。

最终方案：「提权沙箱」（Elevated Sandbox）

1. 引入两个本地用户
Codex 在安装时创建：
· CodexSandboxOffline -- 防火墙规则全封；
· CodexSandboxOnline -- 不被防火墙规则覆盖。
子进程依旧跑在带 【Everyone， Logon， Synthetic】 受限 SID 列表的 write-restricted token 下，但 token 的主体（principal）换成了沙箱用户，而不是真实用户。

5.2 一次性 setup 步骤（需要管理员）
· 创建合成 SID；
· 创建在线 / 离线沙箱用户；
· 凭据用 DPAPI 加密存储，沙箱用户自己读不到；
· 为 CodexSandboxOffline 创建"封禁所有出站"的防火墙规则；
· 给沙箱用户补 读 ACL--因为新用户默认读不到其他用户的 profile、C：\Users、C：\Program Files 等常用目录。这一步耗时，异步执行，不阻塞用户。

5.3 为什么需要 codex-command-runner.exe
直觉的流程是：
codex.exe → LogonUserW → CreateRestrictedToken → CreateProcessAsUserW（child）
但在 CreateProcessAsUserW 这一步存在特权墙：以"真实用户"身份是无法可靠地把进程以另一个用户的受限 token 拉起来的。

解法是把流程切成两段：
Part 1（在真实用户侧）
· codex.exe 用 CreateProcessWithLogonW 把 codex-command-runner.exe 以沙箱用户身份拉起（此时还不是受限 token）。
Part 2（已经在沙箱用户侧）
· runner 用 OpenProcessToken 拿到自己的 token；
· GetTokenInformation 取出 logon SID；
· CreateRestrictedToken 构造最终受限 token；
· CreateProcessAsUserW 拉起真正的子进程。

5.4 最终四层架构
· codex.exe -- 普通非提权的 harness；
· codex-windows-sandbox-setup.exe -- 一次性的提权安装；
· codex-command-runner.exe -- 在沙箱用户内造受限 token 并起子进程；
· child process -- 真正受约束的命令。

拆成独立二进制的好处：codex.exe 在其他平台不被 Windows 专属逻辑污染；UAC 边界只在必要时跨越；setup 的长耗时与主进程生命周期解耦。

### 引用推文

> Tibo：We are continuing to invest in making agents work better on Windows. Highly recommend reading David's engineering post on our unique approach to windows sandbox...
