在 Windows 上为 Codex 构建安全沙箱:文件隔离与网络管控实战

2026-05-14 21 预计阅读时间:1 分钟
来源:openai.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:13 分钟

让 AI 编程代理在你机器上跑代码,听起来既兴奋又危险——兴奋的是效率飞跃,危险的是它可能删文件、扫网络、甚至执行恶意指令。OpenAI 在把 Codex 推向 Windows 时,直面了这个核心矛盾:既要给代理足够的操作空间完成真实开发任务,又要把它的破坏半径锁在可控范围内。这篇文章拆解他们如何用沙箱架构平衡这对矛盾,并给出你可以直接拿来用的隔离方案。

为什么 Windows 沙箱是个硬问题

Linux 上做沙箱有成熟工具链:seccomp 过滤系统调用、cgroups 限制资源、namespace 做进程隔离,组合起来就能画出一条清晰的边界。Windows 的隔离传统走的是另一条路——虚拟化优先(Hyper-V、Windows Sandbox),权限控制靠 ACL 和令牌(Token),两者之间的缝隙比 Linux 大得多。

Codex 的场景尤其棘手:它不是跑一个静态脚本就结束,而是持续读写项目文件、安装依赖、运行测试、调用 API。纯虚拟化方案太重——每次任务启动一个 VM,冷启动就要十几秒,磁盘 IO 还要穿透虚拟化层;纯权限方案太薄——Windows ACL 粒度粗,很难做到"允许读 src/ 但禁止读 .env"这种细粒度控制。

OpenAI 的做法是分层叠加:用轻量容器做进程级隔离,在容器内部叠加文件访问白名单和网络出口限制,再在外层用 Windows 特有的安全机制兜底。

文件访问:白名单而非黑名单

核心设计决策:代理只能看到你明确允许的路径,其余全部不可见。这不是"禁止访问某些危险目录"的黑名单思路,而是"只开放工作目录及其子目录"的白名单思路。黑名单永远有漏网之鱼——你忘了禁 %APPDATA%,代理就可能翻到浏览器 cookie;白名单天然更安全,因为你不会意外开放一个不该开放的位置。

实现上,他们把项目目录以受控方式挂载进沙箱容器:

  • 读写挂载:项目源码目录(如 C:\Projects\myapp\src),代理可以修改文件、创建新文件——这是它完成编码任务的前提。
  • 只读挂载:共享配置或依赖缓存目录,代理能读但不能改——防止它污染全局依赖池。
  • 不挂载:用户主目录、系统目录、其他项目目录——代理根本看不到这些路径的存在。

这种挂载策略在 Windows 上有一个额外好处:NTFS 的 Alternate Data Stream(ADS)不会随挂载穿透,代理无法通过 ADS 隐藏恶意数据。

网络限制:按出口类型分级管控

Codex 代理需要联网——拉包、查文档、调 API——但不是所有网络访问都同等必要。OpenAI 把网络出口分成几个等级:

出口类型 策略 原因
HTTPS 出站到指定 API 兄许 Codex 需要调用 OpenAI API、GitHub API 等
包管理器镜像源 允许(限域名) npm/pip 安装依赖,但只走官方镜像
任意出站连接 拒绝 防止数据外泄、SSRF、C2 回连
入站连接 拒绝 代理不应暴露服务端口

实现方式是在容器网络层叠加出站防火墙规则。Windows 容器网络栈基于 Host Network Service(HNS),可以在 HNS 网络对象上直接挂 ACL 规则,比在容器内部装 iptables 更可靠——代理即使拿到容器内 root 也改不了外层 HNS 规则。

实践:用 Windows Sandbox + PowerShell 搭建一个最小开发沙箱

下面的方案不是 OpenAI 的内部实现(那涉及定制容器运行时),而是你可以在自己 Windows 机器上立刻跑起来的同类思路落地版本。它用 Windows Sandbox(轻量 Hyper-V VM)做外层隔离,用启动脚本做内层白名单和网络管控。

第一步:编写 Sandbox 配置文件

创建 CodexSandbox.wsb

<Configuration>
  <VGpu>Enable</VGpu>
  <Networking>Restricted</Networking>
  <MappedFolders>
    <MappedFolder>
      <HostFolder>C:\Projects\myapp\src</HostFolder>
      <ReadOnly>false</ReadOnly>
    </MappedFolder>
    <MappedFolder>
      <HostFolder>C:\Projects\myapp\tests</HostFolder>
      <ReadOnly>false</ReadOnly>
    </MappedFolder>
    <MappedFolder>
      <HostFolder>C:\Projects\shared-cache\npm</HostFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <LogonCommand>
    <Command>C:\Users\WDAGUtilityAccount\Desktop\setup_sandbox.ps1</Command>
  </LogonCommand>
</Configuration>

关键点解释:

  • <Networking>Restricted</Networking>——这是 Windows Sandbox 提供的受限网络模式,只允许出站 HTTPS,禁止入站和任意 TCP/UDP。正好匹配我们上面的分级策略。
  • 源码和测试目录读写挂载,npm 缓存只读挂载——白名单思路的直接体现。
  • LogonCommand 指向沙箱内的初始化脚本,下一步我们会把这个脚本放进映射目录。

第二步:编写沙箱内初始化脚本

创建 setup_sandbox.ps1,放在 C:\Projects\myapp\src 下(它会随映射目录自动出现在沙箱桌面):

# setup_sandbox.ps1 — 沙箱内执行的安全初始化脚本

# 1. 设置 npm 缓存为只读映射目录,避免全局污染
$env:NPM_CONFIG_CACHE = "C:\Users\WDAGUtilityAccount\Desktop\npm"

# 2. 安装项目依赖(网络受限模式下只能走 HTTPS 镜像)
Set-Location "C:\Users\WDAGUtilityAccount\Desktop\src"
npm install --registry https://registry.npmmirror.com

# 3. 用 Windows 防火墙进一步收紧:只允许出站到特定 API 域名
#    Restricted 模式已禁止大部分出站,这里再加一层域名白名单
$allowedDomains = @(
    "api.openai.com",
    "api.github.com",
    "registry.npmmirror.com"
)

foreach ($domain in $allowedDomains) {
    # 解析域名 IP 并添加出站白名单规则
    $ips = [System.Net.Dns]::GetHostAddresses($domain) |
           Where-Object { $_.AddressFamily -eq 'InterNetwork' } |
           ForEach-Object { $_.IPAddressToString }
    foreach ($ip in $ips) {
        New-NetFirewallRule -DisplayName "Allow $domain" `
            -Direction Outbound `
            -Action Allow `
            -RemoteAddress $ip `
            -Protocol TCP `
            -RemotePort 443 `
            -Profile Any
    }
}

# 4. 默认拒绝所有其他出站(Restricted 模式的补充)
New-NetFirewallRule -DisplayName "Block Other Outbound" `
    -Direction Outbound `
    -Action Block `
    -Profile Any `
    -RemoteAddress Any

# 5. 启动代理工作进程
Write-Host "Sandbox initialized. Starting agent..."
# 这里替换为你的实际代理启动命令
# python agent_runner.py --workspace .

第三步:启动沙箱

# 在宿主机 PowerShell 中执行
WindowsSandbox "C:\Path\To\CodexSandbox.wsb"

沙箱启动后:VM 隔离了进程和内核 → 映射目录控制了文件可见性 → Restricted 网络模式限制了出站类型 → 防火墙规则细化到域名白名单 → 四层叠加,代理的破坏半径被压缩到项目目录 + 必要 API 调用。

注意:Windows Sandbox 每次关闭后所有状态清零,映射目录的修改会写回宿主机。如果你不想代理修改写回,把所有映射都设为 ReadOnly=true,然后用版本控制(git)在宿主机侧审查变更后再合并。

进程级隔离的补充:Job Object 限制

Windows 上还有一个容易被忽视的隔离工具——Job Object。即使你不用虚拟化,也可以用 Job Object 把代理进程组锁在资源笼子里:

import ctypes
import ctypes.wintypes

kernel32 = ctypes.windll.kernel32

# 创建 Job Object
job = kernel32.CreateJobObjectW(None, None)

# 设置限制:进程数上限、内存上限、禁止创建新窗口
JOBOBJECT_BASIC_LIMIT_INFORMATION = ctypes.wintypes.JOBOBJECT_BASIC_LIMIT_INFORMATION()
JOBOBJECT_BASIC_LIMIT_INFORMATION.LimitFlags = (
    0x0001  # JOB_OBJECT_LIMIT_ACTIVE_PROCESS
    | 0x0004  # JOB_OBJECT_LIMIT_JOB_MEMORY
    | 0x0100  # JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
)
JOBOBJECT_BASIC_LIMIT_INFORMATION.ActiveProcessLimit = 8        # 最多 8 个子进程
JOBOBJECT_BASIC_LIMIT_INFORMATION.JobMemoryLimit = 512 * 1024 * 1024  # 512 MB 内存上限

# 应用限制
kernel32.SetInformationJobObject(
    job,
    5,  # JobObjectBasicLimitInformation class
    ctypes.byref(JOBOBJECT_BASIC_LIMIT_INFORMATION),
    ctypes.sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION)
)

# 把当前进程加入 Job
kernel32.AssignProcessToJobObject(job, kernel32.GetCurrentProcess())

print("Job Object 限制已生效:最多 8 进程,512MB 内存上限")

这段代码可以直接在 Windows Python 环境中运行。它创建一个 Job Object,限制进程组最多 8 个子进程、512MB 内存,且未处理异常直接终止——防止代理 fork 炸弹或内存泄漏拖垮宿主机。

采纳建议与风险清单

把 AI 代理放进沙箱不是一次性配置,而是持续对抗。几条实操建议:

  1. 从最严策略开始,逐步放宽——先禁止一切网络、只映射一个空目录,确认代理跑不起来后再按需开放。从宽松开始收紧,你永远不知道漏了什么。
  2. 日志一切代理行为——在沙箱内记录文件修改、网络请求、进程创建。审查日志比审查代码变更更全面,因为代理可能执行了你没预料到的命令。
  3. 定期刷新沙箱镜像——代理可能在沙箱内积累状态(安装了不该装的包、修改了配置)。用不可变镜像 + 每次任务重置,消除状态累积风险。
  4. 宿主机侧做最终审查——沙箱内的修改写回宿主机前,用 git diff 或文件哈希比对确认变更范围。代理的输出必须经过人类确认才能进入主分支。

风险边界要清醒认识:

  • Windows Sandbox 的 Restricted 网络模式仍然允许 DNS 出站——代理理论上可以通过 DNS 隧道外泄数据,虽然代价极高。
  • 映射目录的写回是即时的——代理删文件,宿主机立刻丢失。用 git 或影子副本保护关键数据。
  • Job Object 无法阻止代理在允许范围内做"合法但有害"的操作——比如在项目目录内写一个挖矿脚本然后执行它。行为审计是必需的补充层。

沙箱不是银弹,但它是让 AI 编程代理从"演示玩具"走向"生产工具"的必要基础设施。OpenAI 在 Windows 上的实践证明:隔离的粒度决定信任的深度——你越能精确控制代理能看到什么、能连到哪里、能跑多少资源,你就越敢把真实项目交给它。


相关推荐