跳到正文
OpenAI:官网动态·· 2026-05-13精选AI 评分64

OpenAI 工程师详解 Codex Windows 沙箱的构建过程

Building a safe, effective sandbox to enable Codex on Windows

AI 导读

OpenAI Codex 团队工程师撰文详解如何在 Windows 上为 Codex 构建沙箱。Windows 缺少 macOS Seatbelt 或 Linux seccomp 这类开箱即用的隔离能力。

推荐理由

Codex 工程师复盘 Windows 沙箱从原型到正式方案的取舍过程,读者可以了解受限令牌、防火墙规则等机制如何组合落地。

正文 · AI 翻译

当我在 2025 年 9 月加入 Codex 工程团队时,Windows 版 Codex 还没有沙箱实现,这意味着 Windows 用户在使用 OpenAI 的编码代理时,被迫在两个都不理想的选项之间做选择:

  1. 几乎批准编码代理想要运行的每一条命令(甚至包括读取操作),这既低效又烦人。使用 Codex 的一大好处就是你不用自己去做那些繁琐的工作。
  2. 启用完全访问模式:让 Codex 无需批准或限制即可运行所有命令,这消除了摩擦,但代价是失去了监督。

Codex,我们的编码代理,运行在开发者的笔记本电脑上——无论是通过 CLI、IDE 扩展还是桌面应用。它管理着键盘前的人类与云端运行推理的模型之间的对话。

Codex 默认以真实用户的权限运行,这意味着它能做用户能做的一切。这很强大,也可能很危险。编码模型可能会让运行框架在本地执行命令,从运行测试到读取或编辑文件,再到创建 Git 分支,因此 Codex 的默认模式试图在有效性和安全性之间找到合适的平衡。这种默认模式允许 Codex 几乎在任何位置读取文件,并在你的工作区(即你运行 Codex 的目录)内写入文件,除非你明确要求,否则没有互联网访问权限。为了实现对文件写入和网络访问的安全边界自动约束,Codex 需要一个能够真正强制执行这些约束的沙箱环境。

沙箱是一种受限的执行环境。当开发者使用 Codex 时,其计算机的操作系统会以降低的权限启动一个命令,这些约束会沿着进程树向下传播。每条 Codex 命令从一开始就被沙箱化,每个后代进程都保持在同一边界内。

Image 1: Diagram showing Codex sandbox operating-system isolation boundaries.

Codex 需要由计算机操作系统强制实施的隔离功能,才能实现有效的沙箱。一些操作系统提供了能很好实现这一点的工具(例如 macOS 上的 Seatbelt、Linux 上的 seccomp 或 bubblewrap);然而,Windows 目前并未开箱即用地提供这类能力。

为了让 Codex 在 Windows 上和其他平台一样安全、好用,我们需要实现自己的沙箱。

现有 Windows 工具的不足之处

Windows 提供了一些用于隔离的工具和原语。虽然它们都没有完全满足我们的要求,但我们研究了一些潜在的解决方案——即 AppContainer、Windows Sandbox 和强制完整性控制标记。

AppContainer

  • 是什么:AppContainer 是 Windows 原生的沙箱,一种基于能力的隔离模型,专为那些事先确切知道自己需要访问什么的应用程序而构建。
  • 为什么考虑:它很有吸引力,因为它提供了真正的操作系统边界,而不是尽力而为的限制。
  • 为什么不采用:Codex 并不是一个范围严格限定的应用。它驱动着开放式的开发者工作流:shell、Git、Python、包管理器、构建工具,以及代理认为需要的任何其他二进制文件。在实践中,这使得 AppContainer 不适合这个问题。它提供了强隔离,但适用的工作负载范围比“让代理像开发者一样操作”要窄得多。

Windows Sandbox

  • 是什么:Windows Sandbox 是微软的一次性轻量级虚拟机。你会得到一个全新的 Windows 桌面,并带有强隔离边界,你在其中所做的一切都会在会话结束时消失。
  • 为什么考虑:原因显而易见——它比 AppContainer 对任意软件的兼容性强得多,而且从安全角度看,它是一个强得多的盒子。
  • 为什么不采用:Codex 需要直接作用于用户真实的代码检出、工具和环境,而不是在一个需要额外设置和主机/来宾桥接的独立一次性桌面中运行。它还有一个根本性的产品问题:Windows Sandbox 甚至不在 Windows 家庭版 SKU 上提供。

强制完整性控制(MIC)完整性标签

  • 是什么:Windows 有一个称为“完整性级别”的概念,例如低、中、高,用于决定系统对对象和进程的信任程度。基本规则是,低完整性进程不能写入具有更高完整性级别的对象,即使常规 ACL 本来允许也不行。例如,低完整性进程被视为信任度较低,因此 Windows 会阻止它写入普通的中完整性对象,除非这些对象被显式重新标记以允许写入。
  • 为什么考虑:MIC 在纸面上看起来很优雅——以低完整性运行 Codex,将可写根目录重新标记为低完整性,并让 Windows 在其他所有地方强制执行不可写入。这样就能给我们一条非管理员路径,并且背后有真正的操作系统机制支撑。
  • 为什么不采用:与 ACL 一样,完整性标签会修改真实的主机文件系统,而且在这种情况下,语义变化尤其广泛。将工作区标记为低完整性并不仅仅意味着“Codex 可以在这里写入”。它意味着低完整性进程总体上都可以在这里写入。在真实的开发者机器上,这会把用户实际的代码检出变成主机上的低完整性汇聚点,这比只针对某一种沙箱设计授予精心定向的 ACL 风险大得多。即使中完整性的开发者工具仍能继续工作,工作区的底层信任模型也已经以一种难以控制、更难以正当化的方式发生了改变。

在评估了所有选项并认为它们都不可行之后,我们开始设计自己的解决方案,以便为 Windows 用户带来良好的 Codex 体验。

第一个原型:“非提权沙箱”

我们的第一个可用原型结合了多种 Windows 概念和工具,来实现我们所需的隔离。从一开始,一个目标就是让它在不需要提权的情况下工作,也就是说,Codex 不需要仅仅为了设置或运行沙箱而提示用户授予管理员权限。这意味着必须想办法对两件事施加合理限制:文件写入和网络访问。

限制文件写入

如果我们完全不限制文件写入,就会有安全问题。如果我们过度限制文件写入,沙箱就会损害用户生产力,需要不断请求批准。为了解决这个问题,我们依赖两个重要的 Windows 构建块:SID 和写入受限令牌。

SID 让我们可以为沙箱赋予身份

SID,即安全标识符,是 Windows 与权限关联的身份。每个用户都有一个 SID,组有 SID,甚至单个登录会话也有自己的 SID。例如,当前已登录会话可能有一个类似 S-1-5-5-X-Y 的 SID。分配给本地管理员组的 SID 可能是 S-1-5-32-544。

Windows 还允许你创建合成 SID,这些 SID 不对应真实用户,但仍可出现在 ACL(访问控制列表)中,ACL 定义了谁可以读取/写入/执行特定文件或目录。这使得 SID 成为我们沙箱的一个有用原语:我们可以专门为 Codex 沙箱创建 SID,而不会干扰机器上的其他任何内容。

写入受限令牌限制了 Codex 可以修改文件的位置

进程令牌是 Windows 中的安全对象,用于定义正在运行的进程的身份和权限。它们决定了进程可以执行哪些操作。写入受限令牌是一种特殊类型的进程令牌,它使 Windows 对写入操作执行额外的访问检查。

为了使写入成功,必须通过两项检查:

  1. 普通用户身份(令牌“所有者”)必须被允许执行该操作
  2. 令牌的受限 SID 列表中至少有一个 SID 也必须被授予访问权限

Image 2: Diagram titled Sandbox write requires both regular user access and sandbox-write SID access.

在实践中,这些检查让我们能够使用 ACL 来精确定义沙箱可以在文件系统的哪些位置进行修改,这提供了我们在写入操作方面所需的粒度。

借助 SID 和写入受限令牌,我们的非提权沙箱工作方式如下:

  1. 沙箱设置创建了一个名为 sandbox-write 的合成 SID。

  2. sandbox-write SID 被授予写入、执行和删除访问权限,范围包括:1. 当前工作目录 2. 在 config.toml 中配置的任何额外 writable_roots。

  3. 沙箱设置明确拒绝该 SID 对“可写位置中的只读”位置进行写入访问,例如:1. <cwd>/.git 2. <cwd>/.codex 3. <cwd>/.agents

  4. Codex 在写入受限令牌下启动命令,该令牌的受限 SID 列表包括 Everyone、当前登录会话 SID 以及 sandbox-write 合成 SID。

这一流程有效解决了限制文件写入的问题,看起来很有前景。现在我们需要一个限制沙箱网络访问的解决方案。

限制网络访问

限制网络访问是沙箱的重要组成部分;没有它,恶意代码可以将数据从机器泄露到互联网。由于我们希望避免提权要求,我们在强力阻止网络流量方面的选择有限。我们想使用的工具,如 Windows 防火墙,通常无法在没有管理员权限的情况下安装。

在没有 Windows 防火墙作为选项的情况下,我们限制了我们可以控制的范围。我们尝试让子环境对开发者实际使用的各种网络工具采取故障关闭策略,这样 Git 命令、包安装器等在沙箱中会失败,用户必须批准任何面向互联网的操作。思路是毒化明显的逃生通道:将代理感知流量发送到死端点,让 Git 的 HTTP(S) 传输也这样做,并让基于 SSH 的 Git 立即失败。除此之外,我们在 PATH 前面添加了一个小的 denybin 目录,并重新排序了 PATHEXT,以便存根 SSH 和 SCP 脚本在真实二进制文件之前被解析。

例如,以下是我们用来限制网络访问的一些特定环境覆盖:

  • HTTPS_PROXY=http://127.0.0.1:9
  • ALL_PROXY=http://127.0.0.1:9
  • GIT_HTTPS_PROXY=http://127.0.0.1:9
  • NO_PROXY=localhost,127.0.0.1,::1
  • GIT_SSH_COMMAND=cmd /c exit 1

Image 3: Diagram showing the elevated sandbox architecture with firewall rules and a dedicated Windows user.

这捕获了许多正常的工具驱动流量,但它仍然只是建议性的。进程可以忽略环境、绕过 PATH,或者直接打开套接字——太冒险了。

非提权方法带来了权衡

与任何有趣的软件实现一样,第一个原型既有优点也有缺点。虽然它仅依靠少量标准 Windows 功能就完成了任务,允许非常明确且细粒度的文件系统写入,并且以非提权方式运行——从而减少了用户接受过多提权提示或成为本地管理员的需要——但它也有一些真正的缺点,其中一些使其无法成为我们的最终设计:

  • 设置速度:应用工作区 ACL 可能代价高昂,具体取决于工作区目录的拓扑结构。
  • 占用空间:我们在开发者的系统上应用了真实的 ACL,不过占用空间并不特别具有侵入性,因为所有应用的 ACL 都只涉及一个仅为沙箱使用的自定义合成 SID。
  • 语义难以更改:依赖 ACL 进行基于文件的限制意味着更改沙箱语义既昂贵又复杂。而在 macOS 上,我们可以动态更改生成用于配置 Seatbelt 的 .sbpl 文件的方式,Windows 沙箱则可能需要缓慢而繁重的操作来调整 ACL。
  • 网络保护薄弱。如前所述,它是“建议性”的,肯定会被一些实现了自己网络栈的程序绕过,并且并非设计用来抵御对抗性代码。

前三个问题是自定义沙箱实现所固有的,这种实现足够灵活以支持智能体流程。但网络抑制的情况不同。

网络抑制太重要了

除了恶意智能体能够轻松绕过基于环境的网络抑制之外,许多善意的代码/二进制文件也会绕过它,仅仅是因为它们不遵守环境代理变量,或者因为它们实现了自己基于套接字的网络代码。我们认为这一点足以让我们考虑投资于更好的沙箱模式。

为了获得更好的网络抑制,我们想使用 Windows 防火墙,它允许我们阻止用户或程序的出站网络流量。不幸的是,由于几个原因,我们无法有效创建仅适用于 Codex 框架所生成命令的功能性防火墙规则:

  • Windows 不允许将防火墙规则匹配到受限令牌的非主体身份。这意味着我们无法将防火墙规则应用于“任何在其受限 SID 列表中包含我们合成 SID 的令牌”。
  • 虽然我们可以创建匹配特定二进制文件的防火墙规则,但这只能限制 codex.exe 本身的网络。它不会应用于智能体代表用户生成的进程,例如 Git 或 Python 进程。
  • 其他防火墙匹配维度也不合适。用户范围的规则在非提权设计中仍然匹配真实的 Windows 用户,而不仅仅是受限子进程。程序路径规则过于粗糙:它们可以普遍阻止 codex.exe 或 python.exe,但不能阻止 python.exe 的这一次沙箱化调用。基于端口或地址的规则也完全是错误的策略。例如,我们不想阻止端口 443;我们想阻止这个特定受限进程树的任意出站访问。

为了将防火墙规则专门应用于我们的沙箱化命令,我们需要将它们作为单独的主体运行,而不是作为“真实”用户。这种方法将我们引向了一条新路径,在这条路径上我们放宽了“不提权”约束。

重新设计:“提权沙箱”

沙箱的下一个迭代版本,也就是我们当前的实现,在设置时需要提升的管理员权限。因此我将其称为“提升权限的沙箱”。在 Codex 于系统上生成命令的边界处,提升权限的沙箱看起来与未提升权限的沙箱相同。它仍然在受限令牌下运行子进程——类似地,一个带有相同受限 SID 列表 [Everyone, Logon,``Synthetic``] 的 write_restricted 令牌——然而,此令牌的主体不再是实际的 Windows 用户,而是由 Codex 自身创建的两个本地用户之一:

  • CodexSandboxOffline(防火墙规则所针对的那个)
  • CodexSandboxOnline(防火墙规则未针对的那个)

这个看似微小的细节实际上对沙箱、谁可以使用它,以及其设置和运行时执行的复杂性都有重大影响。

Image 4: Diagram showing network environment overrides for the unelevated sandbox.

它在视觉上与未提升权限的原型相似,但引入了防火墙规则和一个专用的 Windows 用户,该用户实际运行命令。(然而,这些新概念的引入意味着在沙箱开始运行和保护命令之前,有更多的设置工作要做。)

我们现在需要一个一等公民的设置步骤

未提升权限的沙箱设计有一个简单的设置步骤,但相对较小:

  • 如果需要,创建一个合成 SID
  • 为沙箱写入合成 SID 应用 ACL

然而,提升权限的沙箱有更多工作要做。

  • 如果尚未创建,则创建一个合成 SID
  • 如果尚未创建,则创建在线和离线沙箱用户
  • 将新创建的用户凭据本地存储,并使用 Windows 数据保护 API (DPAPI) 加密,放在沙箱用户实际无法读取的位置
  • 创建防火墙规则,阻止 CodexSandboxOffline 用户的所有出站网络访问;如果规则已存在,则验证其正确性

设置阶段还有一个额外的麻烦。Codex 的沙箱预期具有与实际 Windows 用户等效的读取权限。在未提升权限的沙箱中,受限令牌的主体 SID 是 Windows 用户,这已经实现了。然而,当主体变为新的 CodexSandbox 用户时,这并非免费获得。Windows 上的许多相关目录会向“经过身份验证的用户”授予读/执行权限。一个显著的例子是用户的配置文件目录。默认情况下,Windows 用户无法读取其他 Windows 用户的配置文件目录,因此即使在许多场景下简单的文件读取也会失败。

为了解决这个问题,我们在沙箱设置过程中添加了另一层——为沙箱用户授予读取 ACL,在这些 ACL 可能尚不存在的地方。例如,对一些常用的 Windows 目录:

  • C:\Users\<real-user>
  • C:\Windows\
  • C:\Program Files\
  • C:\Program Files (x86)\
  • C:\ProgramData\

由于此目录列表是尽力而为的,并且为每个目录安装 ACL 可能非常昂贵,我们异步运行此逻辑,以便对用户造成阻塞的沙箱设置步骤不必等待它们完成。

我们将设置逻辑封装在自己的二进制文件中,部分原因是为了仅在需要时跨越 UAC 边界。但更深层的原因是架构上的:沙箱设置与 codex.exe 有着根本不同的工作。将沙箱设置逻辑保留在专用二进制文件中,让 codex.exe 保持为正常的、未提升权限的框架;防止仅限 Windows 的设置机制在其他平台上使 codex.exe 膨胀;将运行时间较长的设置工作与主进程的生命周期解耦;并为我们提供了一个处理沙箱所需的不同设置路径的地方。

Image 5: Diagram showing the first-class elevated sandbox setup step.

命令运行器是一个新的二进制文件,它实际运行用户命令

由于 Windows 用户和令牌登录边界的工作方式,我们无法像在非提权沙箱中那样,继续创建一个受限令牌并在其下生成进程。要真正以不同的 Windows 用户身份生成命令,我们最初的想法是以下流程:

  • codex.exe runs as the real Windows user. Then, in a sequence, Codex:
    • 为沙箱用户调用 LogonUserW(...)。
    • 在该沙箱用户令牌上调用 CreateRestrictedToken(...)。
    • 使用该受限沙箱用户令牌,调用 CreateProcessAsUserW(...) 来启动最终子进程。

实际上,由于 CreateProcessAsUserW(...) 处的权限壁垒,这一理想流程无法工作。这意味着 codex.exe 可以为沙箱用户创建受限令牌,但无法可靠地从边界的真实用户一侧使用该令牌启动子进程。我们需要一个已经以沙箱用户身份运行的进程——这样限制步骤和最终生成就可以发生在边界的沙箱用户一侧,而不是真实用户一侧。

这一需求催生了 codex-command-runner.exe,这是一个新二进制文件,其唯一职责是铸造受限令牌并生成所请求的命令。我们没有要求 codex.exe 自己完成整个流程(真实用户 → 沙箱用户 → 受限令牌 → 子进程),而是将流程拆分为两部分:

第 1 部分

  • codex.exe 调用 CreateProcessWithLogonW(...) 以沙箱用户身份启动 codex-command-runner.exe,此时尚未使用受限令牌。

第 2 部分

  • 在运行器内部,OpenProcessToken(GetCurrentProcess(), ...) 打开运行器自身的令牌,该令牌已属于沙箱用户。
  • 运行器调用 GetTokenInformation(...) 提取沙箱登录 SID,然后调用 CreateRestrictedToken(...) 构建最终的受限令牌。
  • 仍在运行器内部,它使用该受限令牌调用 CreateProcessAsUserW(...) 来启动真正的子进程。

Image 6: Diagram showing the command runner flow for spawning restricted commands.

全貌

阿尔伯特·爱因斯坦说过:“一切都应尽可能简单,但不能过于简单。”本着这一精神,我们的设计充分解决了每个问题。最终架构包含我们之前介绍过的四层:

  • codex.exe 本身
  • codex-windows-sandbox-setup.exe 用于处理所有与提权设置相关的工作
  • codex-command-runner.exe 用于运行受限令牌命令
  • 子进程

当我最初接触这个项目时,我并不清楚它最终会走向何方。我的方法是先从 Codex 与操作系统之间的边界处对沙箱能力进行插桩。这种方法与 Codex 沙箱在 MacOs 和 Linux 上的实现方式非常接近。

随着我对 Windows 提供的具体工具了解得越来越多,并通过数十次在安全性和易用性之间权衡的决策,系统逐渐发展成现在的形态——多个二进制文件、自定义用户、防火墙规则、提权设置步骤、异步进程等等。

它并不是一个特别简单的系统,但每一处复杂性都是出于必要而添加的,目的是构建一个既安全、又尽可能不碍用户事的沙箱。

Image 7: Diagram showing the final Windows sandbox architecture.

在安全性与实际实用性之间取得平衡

为了给 Windows 上的 Codex 用户提供良好的体验,我们的目标是做出既安全又不牺牲实用性的东西——使用 Codex 的全部意义就在于让智能体能够在无需你时刻关注的情况下完成工作。

这个项目最大的教训之一是,Windows 并没有给我们提供一个能干净利落地映射到“安全的自主编码智能体”的原语。我们组合了多种工具和概念来构建一个连贯的整体。一些早期想法是死胡同。最终设计是早期原型的混合体,每个原型都解决了问题的一部分。

另一个教训是,编码代理的安全性与更传统的应用安全截然不同。Codex 必须适用于真实的开发者工作流。工程工作在于平衡与代理工作负载的兼容性和真正的强制执行。这种张力塑造了最终设计中的权衡。

想看看 Codex 沙箱的实际运行效果吗?试试看⁠。

来源:OpenAI:官网动态 · openai.com