一声棒喝,本不立文字
偏要著録,已是二义

OpenAI · 官方博客

构建安全有效的沙箱,在 Windows 上启用 Codex

Building a safe, effective sandbox to enable Codex on Windows

二〇二六年五月十三日 · 英文原文

OpenAI的Codex工程团队为Windows平台实现了编码agent沙箱,以解决Windows用户在使用Codex时缺乏安全执行环境的问题。团队评估了AppContainer、Windows Sandbox和强制完整性控制标签等现有工具,发现均不适用。首个原型采用非提权沙箱,通过合成SID和写限制令牌控制文件写入,但网络限制仅依赖环境变量,易被绕过。最终设计为提权沙箱,创建专用Windows用户(CodexSandboxOffline/Online)并配合防火墙规则,通过codex.exe、codex-windows-sandbox-setup.exe和codex-command-runner.exe三个二进制文件协同工作,在安全性与可用性间取得平衡。

当我于2025年9月加入Codex工程团队时,Codex for Windows还没有沙箱实现,这意味着Windows用户在使用OpenAI的编码agent时,被迫在两种次优选项之间做出选择:

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

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

默认情况下,Codex以真实用户的权限运行,这意味着它可以做用户能做的一切事情。这既强大又潜在危险。编码模型可能会指示harness在本地运行命令,从运行测试、读取或编辑文件,到创建Git分支,因此Codex的默认模式试图在有效性和安全性之间找到合适的平衡。这种默认模式允许Codex在几乎所有地方读取文件,并在你的工作区(即你运行Codex的目录)内写入文件,除非你指定需要,否则无法访问互联网。为了在安全范围内自动实现对文件写入和网络访问的约束,Codex需要一个能够实际执行这些约束的沙箱环境。

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

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

为了让Codex在Windows上的使用与其他地方一样安全且令人愉悦,我们需要实现自己的沙箱。

现有Windows工具的不足之处

Windows提供了一些用于隔离的工具和原语。虽然它们都不完全符合我们的要求,但我们研究了许多潜在的解决方案——即AppContainer、Windows Sandbox和强制完整性控制(MIC)标签。

AppContainer

Windows Sandbox

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

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

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

我们的第一个工作原型结合使用了Windows的概念和工具来实现我们需要的隔离。从一开始,一个目标就是使其无需提权即可工作,这意味着Codex不需要仅仅为了设置或运行沙箱而提示用户授予管理员权限。这意味着要弄清楚如何对两件事施加合理的限制:文件写入和网络访问。

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

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

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

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

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

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

在实践中,这些检查让我们能够使用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脚本能在真实二进制文件之前被解析。

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

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

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

前三个问题是自定义沙箱实现所固有的,这种实现需要足够灵活以支持agentic工作流。然而,网络抑制的情况则不同。

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

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

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

重新设计:“提权沙箱”

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

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

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

非提权沙箱设计的设置步骤很简单,但相对较少:

然而,提权沙箱需要做更多事情。

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

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

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

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

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

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

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

第1部分

第2部分

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

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

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

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

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

为了为Windows上的Codex用户提供良好的用户体验,我们的目标是构建一个安全且不牺牲可用性的东西——使用Codex的全部意义在于让agent能够在无需你持续关注的情况下完成工作。

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

另一个教训是,编码agent的安全性与更经典的应用程序安全性是不同的事物。Codex必须适用于真实的开发者工作流程。工程工作在于平衡与agentic工作负载的兼容性和实际执行。这种张力塑造了最终设计中的权衡。

想看看Codex沙箱的实际效果吗?立即尝试

译自 OpenAI · 官方博客 · 录于 二〇二六年五月十三日