Codex Windows 沙箱深度解析 — 为一个 AI 编程 Agent 构建操作系统级隔离
“2025 年 9 月我加入 Codex 工程团队时,Codex for Windows 还没有沙箱实现。Windows 用户被迫在两个糟糕的选项之间选择:要么批准 agent 想运行的几乎每个命令,要么启用全权限模式,信任模型不会作恶。两种体验都很差,而且完全可以避免。”
这段话是 David Wiesen 说的,他花了几个月时间设计 Codex 的 Windows 沙箱。他那篇详细的工程博客完整记录了整个过程——这也是目前关于“如何在一个没有现成方案的操作系统上为 agent 构建沙箱”这个问题的最佳记录之一。
本文不是翻译。本文是对工程决策的结构化分析:为什么在每个关口选这条路而不是那条,哪些模式在 Windows 之外依然成立,以及如果你自己在构建沙箱化 agent 运行时该偷什么。
威胁模型
在讨论实现之前,有必要把威胁模型说清楚。Codex 运行在开发者笔记本电脑上,拥有登录用户的权限。Agent 默认可以读文件、写文件、创建进程、访问网络。模型在云端运行并发来命令,harness 在本地执行。
沙箱需要约束两件事:
- 文件写入 — agent 只能在工作区目录内(以及显式配置的可写根目录)写文件。其他地方只读或不可访问。
- 网络访问 — agent 不应能发起任意出站连接。没有这个约束,一个被攻陷或产生幻觉的模型就能外泄源码、环境变量或凭证。
这两个约束不是可选项。它们是让自主编程 agent 可以安全运行的最低限度。而且必须由操作系统强制执行,不能只靠 harness——advisory 级别的限制在进程决定无视它的那一刻就崩溃了。
为什么 Windows 这么难
在 macOS 上,Codex 使用 Seatbelt(macOS 沙箱)。在 Linux 上,seccomp 和 bubblewrap 提供内核级系统调用过滤。两者都是久经考验的原语,可以配置成:“这个进程树可以写这里、连那里,其他都不行。”
Windows 没有等价物。它有强大的隔离原语,但没有一个能干净地映射到“让自主 agent 像开发者一样操作”。
团队评估了三个候选方案:
AppContainer
AppContainer 是 Windows 原生应用沙箱,基于能力(capability)模型。你需要事先声明应用需要什么——文件路径、网络能力、设备访问——Windows 强制执行这些边界。
问题在于:Codex 不是一个职责单一的应用。它驱动的是开放式的开发者工作流:shell、Git、Python、包管理器、构建工具,以及 agent 决定需要的任何其他二进制。为一组未知的工具预先声明能力是不可能的。AppContainer 提供了强隔离,但只适用于比“让 agent 像开发者一样操作”狭窄得多的工作负载。
Windows Sandbox
Windows Sandbox 是微软的可丢弃轻量虚拟机。你得到一个全新的 Windows 桌面,有强隔离边界,会话结束时一切消失。
问题既是技术层的也是产品层的。技术层:Codex 需要在用户的实际 checkout、工具和环境中操作,不是在一个需要 host/guest 桥接的独立桌面内。产品层:Windows Sandbox 在 Windows Home SKU 上根本不可用。单这一点就足以排除。
强制完整性控制(MIC)
MIC 使用完整性级别——低、中、高——来控制哪些进程能写哪些对象。低完整性进程不能写中等完整性对象,即使 ACL 允许也不行。
这个想法很优雅:以低完整性运行 Codex,把可写根目录标记为低完整性,让 Windows 强制执行对其他地方不可写。不需要提权。
致命缺陷:完整性标签是全局的,不是按沙箱边界隔离的。把一个工作区标记为低完整性不只是意味着“Codex 可以写这里”。它意味着该机器上任何低完整性进程都可以写这里。在真实的开发者机器上,这会把用户的实际 checkout 变成一个供整个系统使用的低完整性水池,比给单个沙箱设计授予精准 ACL 危险得多。
这里值得停下来思考:MIC 的失败模式正是“试图将全局系统机制重新用于单沙箱场景”时会发生的。完整性的概念是正确的,但作用域是错的。
V1:非提权原型
三个标准方案都被排除了,团队开始构建自定义解决方案。第一个约束是不提权——Codex 应该不需要弹管理员权限提示就能工作。
文件写入控制:SID 与写入受限令牌
SID(安全标识符)是 Windows 的身份原语。用户有 SID,组有 SID,登录会话有 SID。你还可以创建不与任何真实用户对应但仍能出现在 ACL 中的合成 SID。
沙箱设置创建了一个名为 sandbox-write 的合成 SID,在工作区目录和任何配置的可写根目录上授予写权限,并在 .git、.codex、.agents 子目录上显式拒绝。
Codex 随后在写入受限令牌下启动命令——这种令牌要求任何写操作必须通过两重检查:正常用户身份必须被允许,并且受限 SID 列表中至少有一个 SID 也被授权。受限列表包含 Everyone、登录会话 SID 和合成 sandbox-write SID。
这给了团队粒度精准的写控制能力,仅使用标准 Windows 原语,无需提权。
网络抑制:Advisory 环境变量操控
没有 Windows Firewall(需要提权),团队尝试让子环境做到 fail-closed。他们污染了环境变量:
HTTPS_PROXY=http://127.0.0.1:9ALL_PROXY=http://127.0.0.1:9GIT_SSH_COMMAND=cmd /c exit 1
他们在 PATH 前面插入了 denybin 目录(包含存根 SSH/SCP 脚本),重新排序 PATHEXT 让存根在真实二进制之前解析。
这捕获了大多数正常工具流量。但它是 advisory 的——进程可以忽略环境变量,绕过 PATH,或直接打开原始套接字。
V1 为什么被否决
V1 有真正的优点:不提权、粒度精准的文件写入控制、干净地使用标准 Windows 原语。但网络抑制从根本上来说是坏的。Advisory 限制对抗不了恶意代码,即使是善意的但不遵循代理环境变量的二进制也会泄漏流量。
自定义方案有三个固有问题:ACL 在大工作区上可能很慢,ACL 足迹必须留在真实文件系统上,且修改沙箱语义需要重新应用 ACL。但网络问题是决定性的否定票。
V2:提权沙箱
为了获得强网络抑制,团队需要 Windows Firewall。而要做到把防火墙规则精准指向沙箱化进程树——不是笼统地指向 codex.exe,也不是所有调用的 python.exe——他们需要以不同的 Windows principal 身份运行子进程。
这意味着放弃“不提权”约束。
两个沙箱用户
提权沙箱创建两个本地 Windows 用户:
CodexSandboxOffline— 被防火墙规则锁定,阻止所有出站网络访问CodexSandboxOnline— 不受防火墙规则影响(用户显式启用网络访问时使用)
防火墙规则简单且有效:阻止 CodexSandboxOffline 的所有出站流量。不需要猜测代理变量,不需要拦截 socket 调用,不需要路径操控。操作系统在网络栈层面强制执行。
命令运行器:跨边界架构
以不同用户身份运行子进程引入了一道权限墙。codex.exe 以真实 Windows 用户身份运行。但 CreateProcessAsUserW——以不同用户身份启动进程的 API——要求调用进程已经在该用户的令牌下运行。
解决方案是 codex-command-runner.exe,一个专门的二进制,唯一的工作就是铸造受限令牌并启动所请求的命令。流程分成两部分:
第一部分 — codex.exe 调用 CreateProcessWithLogonW 以沙箱用户身份启动 codex-command-runner.exe(此时尚未使用受限令牌)。
第二部分 — 在运行器内部,它打开自己的令牌(属于沙箱用户),从中构建受限令牌,然后调用 CreateProcessAsUserW 启动真正的子进程(git、python 等等)。
这个分界是整个架构中最关键的洞见。codex.exe 无法直接启动受限子进程,因为它在用户边界的错误一侧。运行器是一个 trampoline:它位于沙箱用户一侧,在那里令牌限制和子进程启动都是被允许的。
读访问问题
当子进程以 CodexSandboxOffline 而非真实用户身份运行时,它会失去对那些只对真实用户做了 ACL 的文件的读访问权限。例如用户的 profile 目录默认不可被其他用户读取。
沙箱设置在常用目录上授予沙箱用户读 ACL:用户的 profile、C:\Windows、C:\Program Files、C:\Program Files (x86) 和 C:\ProgramData。这个操作是异步执行的,因为在大目录树上应用 ACL 很昂贵且不应阻塞用户。
四层架构
最终设计分为四层:
codex.exe— 普通用户态 harness,永不提权codex-windows-sandbox-setup.exe— 运行一次(或配置变更时),处理 UAC 提权,完成用户创建、ACL 授予和防火墙规则codex-command-runner.exe— 每个命令启动一次,跨越用户边界,铸造受限令牌,启动子进程- 子进程 — agent 要求运行的实际命令,现在在沙箱内执行
每个二进制有且只有一个任务。Setup 二进制跨越 UAC 边界,所以 codex.exe 永远不需要。运行器跨越用户边界,所以 codex.exe 永远不需要。架构之所以干净,正是因为每个组件的职责定义得足够锐利。
值得借鉴的设计原则
研究完这个架构后,几个模式浮现出来,它们的适用性远超 Windows:
1. 将 Setup 与 Runtime 分离
沙箱设置(用户创建、ACL 授权、防火墙规则)和启动沙箱化进程是根本不同的工作。分离的二进制让你只在需要的地方跨越提权边界,将平台特定代码保持在主 harness 之外,并将耗时较长的设置工作与主进程生命周期解耦。
2. 用操作系统,不用 Harness
V1 失败是因为基于环境的网络抑制是 advisory 的。V2 成功是因为 Windows Firewall 在内核级别强制执行。当你需要一个安全边界时,使用操作系统。Harness 级别的检查是便利功能,不是安全。
3. 创建合成身份来实现细粒度策略
合成 SID 模式——创建一个只属于沙箱的身份,授予它定向访问权限,然后将进程限制到该身份——干净且可移植。在 Linux 上映射到自定义组或 SELinux 类别。在 macOS 上映射到带自定义 entitlements 的 Seatbelt 扩展。
4. 使用 Trampoline 模式进行跨边界执行
当你需要以不同身份运行代码且当前进程不能直接跨越边界时,插入一个 trampoline。运行器除了切换上下文并交接之外什么都不做。这种模式到处可见:Linux 上的 setuid 二进制、macOS 上的 XPC 服务,以及这里的 codex-command-runner.exe。
5. 对非关键工作使用懒加载/异步设置
读 ACL 授予是异步运行的。如果慢,不会阻塞用户。如果失败,沙箱优雅降级(某些读取失败,用户看到错误,可以重试)。不是每个设置步骤都需要阻塞关键路径。
6. 在线/离线执行分离
双用户模式(Offline vs Online)很聪明:不是按命令动态添加和删除防火墙规则,而是创建两个带有永久规则的身份,在启动时选择合适的那个。这比规则操作更简单、更快、更容易避免出错。
反模式:不该做什么
不要将全局机制用于单沙箱场景
MIC 标签失败是因为完整性级别是全局的,不是按边界隔离的。“工作区对 Codex 是低完整性”变成了“工作区对一切都是低完整性”。当你重新利用系统级机制时,副作用也是系统级的。
不要把 Advisory 控制当作安全
基于环境的网络抑制捕获了正常流量,在测试中看起来不错。但任何依赖进程选择合作的“安全模型”都不是安全模型。它是一个贴了误导标签的便利功能。
不要假设平台功能普遍可用
Windows Sandbox 被排除的部分原因是它在 Windows Home 上不可用。如果你的产品需要在消费者 SKU 上工作,就不要在没有备选方案的情况下依赖仅企业版可用的功能。
不要把 Setup 逻辑塞进主二进制
将 codex-windows-sandbox-setup.exe 与 codex.exe 分开是一个明确的架构决策,而不是实现上的捷径。Setup 有不同的权限要求、不同的失败模式、不同的生命周期。把它们合在一起会把平台特定的 UAC 逻辑污染到主 harness 中。
安全-可用性-性能三角
这个项目是竞争性约束之间张力的案例研究:
| 维度 | 做出的取舍 | 理由 |
|---|---|---|
| 安全 | 提权设置(UAC 提示) | 防火墙集成需要管理员权限;advisory 网络控制不是安全 |
| 可用性 | 异步读 ACL 授予 | 对大目录树做阻塞式设置不可接受 |
| 性能 | 大工作区上 ACL 应用成本 | 接受为一次性设置成本;通过异步执行缓解 |
| 兼容性 | 专用沙箱用户而非复用真实用户 | 启用防火墙定向,但需要显式读 ACL 授予 |
| 复杂度 | 四层架构而非单体设计 | 每层职责清晰;复杂度局部化而非分散 |
最有趣的取舍是提权要求。团队通过整个原型周期都在抵抗它。只有当明确看到强网络抑制需要一个只有提权才能提供的机制时,他们才接受了。这是正确的决策:先把更简单的路径走到底,然后在确实需要时才接受复杂度。
构建 Agent 沙箱的落地检查清单
如果你正在为 AI agent 构建沙箱化执行环境,以下是需要思考的事项:
- 明确定义威胁模型。Agent 不加约束时能做什么?沙箱必须阻止什么?写下来。
- 清点平台上的隔离原语。Linux:namespaces、seccomp、cgroups、SELinux/AppArmor。macOS:Seatbelt、sandbox-exec。Windows:tokens、SID、AppContainer、firewall。
- 先试最简单的路径。非提权原型花了几周,不是几个月。它证明了什么可行(文件写入)和什么不可行(网络)。这比预先设计“完美架构”便宜得多。
- 如果你的网络抑制是基于环境的,它就不是抑制。找到一个内核级机制,或者显式接受风险。
- 分离设置与运行时,提权与正常操作。Harness 永远不应该提权。Setup 二进制只跨越一次 UAC 边界。
- 使用合成身份实现定向策略。不要给真实用户授权然后试图减去权限。创建一个新身份,只授予必要的权限。
- 考虑 trampoline 模式用于跨边界执行。如果不能直接启动,插入一个可以启动的进程。
- 在所有目标 SKU 上测试。Windows Home、Pro、Enterprise。消费者和企业环境。不是所有变体上都能用的功能就不是你能依赖的功能。
- 异步授予读权限。它慢且容忍失败。不要让用户等它。
- 验证限制在
exec、fork和进程树中持续存在。一个只对第一个进程生效的沙箱是坏的。 - 为沙箱语义变更做好计划。基于 ACL 的方案修改起来昂贵。动态配置方案(如 macOS Seatbelt 的
.sbpl文件)演进成本更低。
局限性与适用范围
这个沙箱是为特定威胁模型设计的:一个运行在开发者笔记本电脑上的 AI 编程 agent,主要风险是意外文件修改和数据外泄。它不是为了防御以下情形而设计的:
- 内核漏洞利用或提权攻击
- 硬件级侧信道
- 以更高权限运行的被攻陷系统二进制
设计也假设用户信任 Codex harness 本身。如果 codex.exe 被攻陷,沙箱不提供任何保护——harness 在边界之外。
这些不是批评。每个沙箱都有威胁模型。Codex 团队的模型正确地限定了运行 AI 编程 agent 的实际风险范围,实现也精确匹配了这个范围。
资料来源
- David Wiesen, "Building a safe, effective sandbox to enable Codex on Windows," OpenAI Engineering Blog, 2026 年 5 月 13 日. https://openai.com/index/building-codex-windows-sandbox/
- HN 讨论: Sandboxing Codex on Windows,2 条评论,2026 年 5 月
本文是对 OpenAI 工程博客文章的技术深度解读。架构图为根据原文中 V1 原型与 V2 生产设计描述的原创重构。