文章类型技术长文 所属专栏Agent 前沿技术 预计阅读101 分钟 文档状态已更新
返回

Agent Sandbox 技术原理:本地客户端与 Web 云端执行环境的双重实现

从副作用控制出发,系统拆解本地客户端与 Web 云端 Agent Sandbox 的文件、进程、资源、网络和凭证边界,并以 OpenAI Codex 的跨平台实现说明真实工程链路。

开始阅读全文20294 字 · 101 分钟 查看系列目录Agent 前沿技术
关键词 AI AgentSandboxCodexLinux NamespaceSeccompgVisorFirecracker云端执行
栏目 AI-Coding;专栏 Agent 前沿技术;标签 AI Agent、Sandbox、Codex、Linux Namespace、Seccomp、gVisor、Firecracker、云端执行

当大模型只能生成文本时,最坏的结果通常是一段错误回答;当 Agent 能够执行 Shell、修改文件、安装依赖、调用外部服务时,错误就会从“内容问题”变成真实的系统副作用。Sandbox 的职责,就是在模型意图与操作系统之间砌出一层可执行、可约束、可终止的边界。

引言:Agent 需要的不是一台“随便用的电脑”#

大模型应用正在从对话接口转向执行系统。一个 Coding Agent 接到“修复接口超时并运行测试”的任务后,可能连续完成以下动作:

读取仓库
→ 搜索调用链
→ 修改源码
→ 安装依赖
→ 执行编译
→ 启动测试进程
→ 访问网络
→ 提交 Git 变更

这些动作不再只是 Token。它们会创建进程、打开文件、消耗内存、建立网络连接,并可能把系统从状态 A 推到状态 B。

更棘手的是,最终命令不只受用户指令影响。模型还会读取仓库中的 READMEAGENTS.md、Issue、依赖文档、网页和工具返回值。任何一段外部内容都可能夹带错误步骤,甚至包含 Prompt Injection。于是,一个看似普通的“运行项目测试”,背后可能悄悄变成“读取 Git 历史并上传到外部地址”。

因此,Agent 安全不能建立在“模型应该不会这么做”之上,而应建立在另一个假设上:

模型迟早可能生成错误甚至危险的动作,但这个动作造成的影响必须被锁在一个明确、可观察、可销毁的边界中。

这个边界就是 Agent Sandbox。

从用户指令、LLM 推理和 Tool Call,经 Agent Harness 与受控沙箱执行,最终产生文件、进程、网络等操作系统副作用的完整链路

图 1:模型只生成意图和 Tool Call;真正产生副作用的是处于文件、进程、网络、资源和审计边界内的执行环境。


一、Agent Sandbox 隔离的到底是什么#

1.1 沙箱隔离的不是推理,而是副作用#

一个完整 Agent 系统通常可以拆成三个角色:

LLM Provider
负责生成推理结果和 Tool Call
Agent Harness
负责循环、规划、上下文和工具编排
Sandbox Runtime
负责真正执行命令并限制副作用

LLM Provider 只负责计算下一步应该做什么;Agent Harness 把模型输出组织成任务状态和工具调用;真正危险的部分发生在最后一层,因为 Shell、Python、Git、编译器、浏览器和包管理器都要通过操作系统完成真实动作。

所以,大多数情况下并不需要把模型推理服务本身塞进沙箱。真正要进入沙箱的是模型触发的执行单元,包括:

  • Shell 与脚本解释器;
  • Python、Node.js、JVM 等语言运行时;
  • Git、Maven、npm、pip 等开发工具;
  • 编译器、测试进程和后台服务;
  • 浏览器自动化环境与 Computer Use 进程;
  • 可能包含第三方代码的 MCP Server 或插件运行时。

Agent Harness 可以位于沙箱外,但它启动的每一个命令、子进程和孙进程都必须从出生起就处于同一条安全边界内。否则,限制了最外层 bash,却放任它启动的 pythonnode 逃到宿主权限中,沙箱就只剩一张纸门。

1.2 Sandbox 与 Approval 是两套不同控制#

Agent 产品经常把“沙箱”和“用户审批”放在同一个界面中,容易让人误以为二者是一回事。实际上,它们回答的是两个不同问题:

Sandbox:
操作系统技术上允许这个进程做什么?
Approval:
当 Agent 想超出当前权限时,是否需要暂停并询问用户?

例如,一个本地 Coding Agent 可以被允许在项目目录内读写,但默认不能访问网络:

读取 /workspace/src → Sandbox 内允许
修改 /workspace/src → Sandbox 内允许
读取 ~/.ssh/id_rsa → Sandbox 拒绝
访问 package registry → 触发 Approval
访问云元数据服务 → 永久拒绝

审批通过也不应该等价于“关闭全部限制”。更稳妥的方式是只扩大一项具体能力,例如在本次命令中允许访问 pypi.org,而不是把 Agent 瞬间变成拥有整台机器权限的超级用户。

OpenAI 对 Codex 的公开定义也明确区分了两层:Sandbox 决定技术边界,Approval Policy 决定越过边界前何时询问;同时,Git、包管理器、测试运行器等派生命令会继承相同的沙箱限制。1

1.3 Agent Sandbox 的五类控制目标#

从技术角度看,沙箱至少需要回答五个问题:

控制面核心问题典型风险
文件系统Agent 能看到、读取和修改哪些路径删除源码、读取密钥、修改系统配置
进程Agent 能启动什么,子进程是否仍受约束后台进程残留、进程逃逸、ptrace
资源Agent 最多消耗多少 CPU、内存和进程数死循环、OOM、Fork Bomb
网络Agent 能连接哪些地址和服务数据外传、内网扫描、访问元数据服务
凭证Agent 如何代表用户调用外部系统Token 泄露、越权 Push、长期密钥滥用

这五类控制不是互相独立的。例如,文件权限阻止 Agent 读取密钥,网络权限阻止已读取的数据被送出去,凭证代理则避免真实密钥从一开始就进入进程环境。真正可靠的沙箱往往来自多层限制的叠加,而不是某个“开启安全模式”的按钮。


二、本地客户端沙箱与 Web 云端沙箱#

“Agent 在 Web 端运行”和“Agent 在客户端运行”经常被混在一起讨论,但二者的执行位置和保护对象完全不同。

2.1 本地客户端沙箱#

本地客户端包括 CLI、IDE 插件和桌面应用。它们的典型链路是:

用户本地项目
CLI / IDE / Desktop Agent
本地 Agent Harness
操作系统沙箱
本地 Shell、Git、编译器和测试工具

这种模式最大的优势是 Agent 可以直接使用用户现有开发环境:本地未提交修改、编译缓存、语言工具链、IDE 配置以及项目附近的依赖服务都触手可及。

但它的风险半径也非常现实。Agent 与用户的 SSH Key、云凭证、浏览器 Cookie、公司 VPN、Docker Daemon 和私人文件处于同一台机器上。沙箱的中心任务是:

允许 Agent 深入项目目录工作,同时阻止它横向进入用户电脑的其他区域。

2.2 Web 入口下的云端沙箱#

Web 模式通常不是让 Shell 在浏览器标签页里运行。主流云端 Agent 的浏览器页面只是任务入口和观察界面,真正的执行发生在服务端隔离环境中:

Browser
↓ HTTPS / Task API
Cloud Agent Service
Cloud Sandbox
远端仓库副本、Shell、编译器和测试进程

云端沙箱通常先创建容器或虚拟机,再 Clone 仓库、准备依赖、启动 Agent。用户最后看到的是日志、Diff、Commit、Pull Request 或构建制品,而不是云主机本身。

以 Codex 为例,当前环境模式明确区分 Local、Worktree 和 Cloud。Local 与 Worktree 都在用户电脑上运行,Cloud 则在配置好的远端环境中运行。2

2.3 浏览器原生沙箱是第三种概念#

还有一类技术确实在浏览器内部执行代码,例如:

iframe sandbox
Web Worker
WebAssembly / WASI
Pyodide
WebContainers
浏览器 Origin 隔离

它们依赖浏览器进程模型和 Web 权限体系,适合运行受限 JavaScript、Wasm 或部分语言环境。但对于需要完整 Linux 工具链、Docker、系统编译器或复杂后台服务的 Coding Agent,浏览器通常只承担控制平面,重执行仍然放在服务器端。

因此,本文后续使用“Web 云端沙箱”这个称呼,特指:

由 Web 页面发起,但在服务端容器、gVisor 或虚拟机中执行的 Agent Sandbox。

2.4 同一个 Sandbox,城墙朝向不同#

本地和云端沙箱共享很多底层积木,但它们的首要保护对象不同:

本地客户端沙箱:
防止 Agent 越过项目边界,进入用户真实电脑
Web 云端沙箱:
防止任务越过租户边界,进入云宿主、控制面或其他任务

本地沙箱包裹的是“真实工作区”;云端沙箱包裹的是“任务工作副本”。这一区别会一路影响文件挂载、进程身份、凭证来源、网络策略和状态保存方式。

本地客户端沙箱与 Web 云端沙箱的双形态边界对比

图 2:本地沙箱从真实用户权限中做减法,重点保护用户设备;云端沙箱从临时任务身份开始做加法,同时保护平台和其他租户。


三、两种沙箱共同依赖的操作系统地基#

无论 Agent 最终运行在用户笔记本还是云端节点上,它都需要把模型生成的命令转换成一个受限进程。Linux 上最常见的技术组合是 Namespace、cgroups、Capabilities、no_new_privs、Seccomp 和 LSM。

3.1 Command Runner:沙箱真正落地的边界#

错误的执行方式是让 Harness 直接调用宿主进程 API:

Tool Call
ProcessBuilder / subprocess / exec
Host Shell

此时 Agent 生成的命令与普通用户进程拥有同样的视野和权限。更合理的结构是加入一个专门的 Sandbox Launcher 或 Command Runner:

Tool Call
Policy Check
Sandbox Launcher
├── 创建 Namespace
├── 设置挂载和身份
├── 加入 cgroup
├── 移除 Capabilities
├── 加载 Seccomp / LSM
└── 清理环境变量
Restricted Child Process

沙箱不是命令执行后的补丁,而是命令启动前就确定好的出生环境。理想情况下,进程从 execve() 的第一条指令开始便没有机会接触宿主完整权限。

从实现上看,Policy Layer 不应直接输出一句模糊的“允许执行”,而应编译成一份可交给操作系统执行的 Launch Plan:

Executable /usr/bin/python3
Argv [python3, scripts/check.py]
WorkingDir /workspace/project
WritableRoots [/workspace/project, /tmp]
ReadableRoots [/usr, /lib, /workspace/project]
DeniedPaths [~/.ssh, **/.env]
Network off / proxy-policy-id
ResourceGroup cgroup-agent-run-42
Timeout 300s
Environment allowlisted variables only

这个区分非常重要。模型收到的权限说明、Harness 做出的策略判断和内核真正执行的约束属于三层:

Prompt 中的权限说明 帮助模型少发出越权动作
Policy Engine 判断本次请求应允许、拒绝还是审批
OS Sandbox 即使模型或策略层出错,也负责最后强制执行

只把“不要读取某目录”写进 Prompt,属于行为引导,不属于安全边界;只在 Harness 中检查一次路径,也可能被子进程、符号链接或替代工具绕开。最终权限必须被翻译为挂载、身份、ACL、网络和系统调用等操作系统原语。

3.2 Namespace:给进程制造一套局部世界#

Linux Namespace 会虚拟化一部分全局系统资源,使不同进程看到不同的系统视图。常见类型包括 PID、Mount、Network、User、IPC、UTS 和 Cgroup Namespace。3

PID Namespace#

PID Namespace 为沙箱建立独立进程编号空间:

宿主看到:PID 24631
沙箱看到:PID 1

配合在该 PID Namespace 内重新挂载 /proc 且不暴露宿主 procfs,沙箱进程才看不到宿主完整进程树,也不能仅凭 PID 定位其他租户进程。沙箱中的 PID 1 还承担子进程回收和信号处理职责。如果 PID 1 设计不当,僵尸进程就可能像灰尘一样积在角落。

Mount Namespace#

Mount Namespace 为进程提供独立挂载表。沙箱可以把根文件系统设置为只读,只暴露一个可写工作区:

/
├── /usr read-only
├── /bin read-only
├── /etc read-only
├── /tmp private tmpfs
└── /workspace writable

宿主的 ~/.ssh、浏览器数据和其他项目目录根本不必出现在沙箱视图中。相比“路径存在但禁止访问”,直接让路径不可见通常更干净。

Network Namespace#

Network Namespace 为沙箱提供独立网络设备、地址、路由表、端口空间和防火墙视图。常见做法是通过一对 veth 设备把沙箱接到宿主网络:

Sandbox netns
eth0
veth
Host bridge / firewall / proxy

这样,平台可以从路由层阻止沙箱直接访问宿主、内网或公网,只留下通往受控代理的一条窄门。

User Namespace#

User Namespace 虚拟化 UID、GID 和 Capabilities。沙箱内部的 UID 0 可以映射到宿主上的普通非特权 UID:

Sandbox UID 0
↓ uid_map
Host UID 100000

这意味着“沙箱内 root”只在该 User Namespace 管辖的资源上拥有能力,并不天然等于宿主 root。4

Namespace 之间还存在组合细节。创建 Mount Namespace 后,通常要把挂载传播改成 privateslave,防止沙箱内后续挂载反向传播到宿主;创建 PID Namespace 后,真正进入新 PID 空间的是随后 fork() 出来的子进程;User Namespace 的 UID/GID 映射又决定了进程能否创建其他 Namespace 和执行挂载操作。它们更像一组有装配顺序的齿轮,而不是几枚互不相干的开关。

Rootless 沙箱也并非天然无风险。非特权 User Namespace 能显著降低宿主身份权限,但沙箱进程仍与宿主共享内核。如果内核允许的接口过多,攻击面仍可能从 Namespace 内一路延伸到宿主。因此,User Namespace 通常还要与 Capability 删除、Seccomp 和 LSM 共同使用。

3.3 cgroups:限制这套局部世界能吃多少资源#

Namespace 解决“看见什么”,cgroups 解决“最多能用多少”。cgroup v2 可以把一个任务的所有进程组织进同一层级,并使用控制器限制 CPU、内存、进程数和块设备 I/O。5

常见接口包括:

cpu.max CPU 时间配额
cpu.weight 竞争时的调度权重
memory.high 内存软压力阈值
memory.max 内存硬上限
pids.max 最大进程数量
io.max 块设备 I/O 限制

Agent 场景中,pids.max 尤其重要。Fork Bomb 可以在超时机制来得及反应前快速创建大量进程,耗尽系统 PID 和调度资源。执行超时控制的是“运行多久”,pids.max 控制的是“能长出多少枝杈”,两者不能互相替代。

所有后代进程都应留在同一个 cgroup 中:

bash
└── npm
└── node
└── child_process

任务取消时,系统需要终止整个 cgroup,而不是只杀最外层 Shell。否则后台服务可能已经脱离父进程,继续占用端口和资源。

内存限制也不只是设置一个 memory.max。比较完整的控制通常会同时观察:

memory.current 当前使用量
memory.high 进入回收与节流的压力线
memory.max 硬上限
memory.events high、max、oom、oom_kill 计数
memory.oom.group OOM 时是否按任务组整体处理

如果只让内核随机杀死进程树中的某个测试 Worker,父进程可能把它当作普通失败并持续重启,形成“被杀死又复活”的资源震荡。把 OOM 结果映射成任务级错误,并在必要时终止整个资源组,才符合 Agent Run 的语义。

同理,RLIMIT_NOFILERLIMIT_FSIZE 和文件系统 Quota 仍然有价值。cgroups 主要控制资源组,传统 rlimit 控制单进程或进程继承上限;二者叠加可以阻止 Agent 用大量文件描述符、超大单文件或无限日志绕过粗粒度 CPU、内存配额。

3.4 Capabilities 与 no_new_privs#

Linux 把传统 root 权限拆成多个 Capability,例如:

CAP_SYS_ADMIN
CAP_NET_ADMIN
CAP_SYS_PTRACE
CAP_SETUID
CAP_SETGID
CAP_DAC_OVERRIDE

其中 CAP_SYS_ADMIN 覆盖的能力极广,经常被称作“新的 root”。Agent 沙箱通常应从 Drop All Capabilities 开始,仅在确有必要时补回极少数能力。

no_new_privs 只保证调用线程及其随后创建的后代在后续 execve() 时,不能因为 setuid/setgid 位、文件 Capability 或 LSM 的执行转换获得新权限;现有的兄弟线程不会自动同步该属性,它也不代表进程的所有权限状态都会单调收紧。多线程 Runner 应在创建不可信线程前设置,或逐线程确保覆盖。它是阻断“执行新程序时提权”的关键棘轮,仍要与 Capability 删除、Namespace 和 LSM 等机制叠加。

3.5 Seccomp-BPF:缩小系统调用入口#

即使文件视图和身份已经隔离,进程仍然要调用宿主内核。Seccomp Filter 可以在系统调用入口根据 syscall 编号及参数返回 ALLOW、ERRNO、TRAP、KILL 或 USER_NOTIF 等结果。过滤器会被之后创建的 fork()clone() 子进程以及 execve() 继承;要覆盖整棵进程树,必须在启动不可信线程和子进程前安装过滤器,或用 SECCOMP_FILTER_FLAG_TSYNC 同步到现有线程。6

Agent 沙箱通常会重点限制:

mount / umount
ptrace
bpf
keyctl
perf_event_open
init_module / finit_module
open_by_handle_at
reboot

但 Seccomp 不是完整权限系统。它很难直接表达“允许读取 /workspace,禁止读取 ~/.ssh”这种对象级规则。Seccomp 更像门卫,决定能不能使用某类内核入口;至于进入后能访问哪个具体文件,还要交给挂载权限、传统 DAC 和 LSM。

SECCOMP_RET_USER_NOTIF 还允许把某些系统调用暂停并转交给用户态监督进程,主要用于由更高权限监督器委托、模拟 syscall 或伪造返回值。它不能作为安全授权边界:SECCOMP_USER_NOTIF_FLAG_CONTINUE 存在不可消除的 TOCTOU,目标线程可能在等待期间改写指针参数;只有另一个内核安全机制仍会拦住危险参数时,监督器才应让原调用继续。把大量 syscall 都绕到用户态还会迅速放大性能与竞态复杂度。7

Seccomp Profile 还要考虑兼容性。编译器、JVM、浏览器和包管理器调用的内核接口差异很大,过窄会让正常工具随机报错,过宽又会把攻击面重新撑开。常见策略是从经过验证的基线 Profile 出发,只为明确的工具链补充调用,并把 mountptrace、内核模块、BPF 等高风险能力继续留在边界外。

3.6 LSM 与 Landlock:控制具体对象#

AppArmor、SELinux 和 Landlock 都属于 Linux Security Module 体系。它们可以在传统文件权限之外,再叠加一层强制访问控制。

Landlock 对本地 Agent 特别有价值,因为普通非特权线程可以主动限制自身及随后创建的后代,并且规则只能继续收紧。现有兄弟线程默认不会自动进入同一 Landlock Domain:ABI v8 及以上可使用 LANDLOCK_RESTRICT_SELF_TSYNC 同步,旧 ABI 上则应在创建其他线程前施加规则。非特权调用者通常需要先设置 no_new_privs;ABI v11 及以上也可用 LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS 把两步原子化。Landlock 可以限定允许读取或写入的目录,并与系统原有安全策略叠加。8

把这些机制放在一起,它们的分工非常清楚:

Namespace 隔离系统视图
cgroups 限制资源数量
Capabilities 拆分和移除特权
no_new_privs 阻止 execve 新增权限
Seccomp 缩小系统调用面
LSM 限制具体对象访问

强沙箱不是一堵厚墙,而是一组前后错位的闸门。某一层出现缝隙时,下一层仍有机会把动作挡住。

Linux Sandbox 中 Namespace、cgroups、Capabilities、Seccomp 与 LSM 的分层关系

图 3:Linux 沙箱不是单一机制,而是由可见性、资源、特权、系统调用和对象访问五层控制共同组成。

3.7 文件系统:只读 RootFS、OverlayFS 与安全路径解析#

云端沙箱和容器环境常把基础系统层设为只读,再叠加每个任务独立的可写层:

LowerDir 基础镜像,只读
UpperDir 当前沙箱的修改
WorkDir OverlayFS 内部工作目录
MergedDir Agent 实际看到的视图

当 Agent 修改来自 LowerDir 的文件时,OverlayFS 会把对象 Copy Up 到 UpperDir,再在上层修改;删除下层文件时则通过 Whiteout 表达“在合并视图中不可见”。基础镜像仍保持原样,不同任务可以共享同一份只读层。9

OverlayFS 只提供写层和 Copy-on-Write 状态隔离,本身不是租户安全边界。每个沙箱应拥有独立的 UpperDir 与 WorkDir,并继续依赖身份、挂载、LSM 或虚拟化约束。

本地沙箱更常把用户项目目录以 Bind Mount 或平台 ACL 的方式映射进沙箱,同时让其他目录只读或不可见。

仅在应用层判断路径字符串是不够的:

if (path.startsWith("/workspace")) {
allow();
}

../、符号链接、Magic Link 和 TOCTOU 都可能让“检查时安全”的路径在打开时指向边界之外。更可靠的做法是先打开工作区目录,再围绕目录文件描述符调用 openat2():根据所需语义选择 RESOLVE_BENEATHRESOLVE_IN_ROOT,并叠加 RESOLVE_NO_MAGICLINKSRESOLVE_NO_SYMLINKS,必要时再加 RESOLVE_NO_XDEV。如果只能使用普通 openat(),则要逐级解析目录 FD 并配合 O_NOFOLLOW。Hard Link 属于同一 inode 的别名风险,需要通过挂载/文件系统权限、LSM 以及限制创建链接单独治理。10

文件描述符本身也是能力。父进程如果在进入沙箱前打开了敏感文件、目录或 Socket,再把 FD 继承给子进程,那么路径即使在 Mount Namespace 中不可见,子进程仍可能通过已有 FD 继续访问对象。因此 Command Runner 需要关闭非白名单 FD,或使用 close_range()FD_CLOEXEC 等机制保证 execve() 后只留下标准输入输出和显式控制通道。

对 Coding Agent 而言,文件系统还承担结果边界。平台可以在任务前记录 Git 状态、OverlayFS UpperDir 或文件清单,任务后生成:

Created Files
Modified Files
Deleted Files
Mode Changes
Symlink Changes
Git Diff

这不是为了替代沙箱,而是让“沙箱内发生了什么”变成可审查输出。文件隔离负责限制动作,文件差异负责解释动作。

3.8 网络与凭证:两条必须分开的控制链#

只设置 HTTP_PROXY 并不等于断网。程序可以忽略环境变量,直接创建 Socket。真正的网络隔离应该发生在 Network Namespace、路由、防火墙或云网络策略层:

Sandbox Process
Network Namespace
Default Deny Firewall
Egress Proxy
Allowed Domain / API

代理可以做域名白名单、最终 IP 校验、HTTP 方法限制、重定向复检和流量审计。还要显式阻止回环地址、私有网段、宿主网关、云元数据服务、Kubernetes API 和其他沙箱网段。

凭证则应沿另一条链路处理。把真实 GitHub Token、AWS Key 或数据库密码放进环境变量,并不会让它们变安全;Agent 启动的第三方脚本可以通过环境变量或 /proc 读取它们。更稳妥的结构是:

Sandbox
↓ 语义操作请求
Credential Broker / MCP Gateway
↓ 身份、目标与操作校验
短期凭证或代理执行
External Service

授权可以绑定到 sandbox_id、仓库、分支、操作、有效期和最大使用次数。这样,沙箱得到的是“向指定分支 Push 一次”的能力,而不是一把长期万能钥匙。


四、本地客户端沙箱的技术实现#

本地客户端可以是 CLI、IDE 插件或桌面应用。它面对的不是一台干净的临时机器,而是一台已经登录了用户身份、保存着真实源码、凭证、浏览器状态和公司网络连接的电脑。因此,本地沙箱最难的地方并不是启动一个命令,而是让命令足够接近项目,同时又与用户其余环境断开。

4.1 本地执行面应该如何分层#

一个较完整的本地 Agent 不应让 UI 进程或 Agent Harness 直接调用宿主 Shell,而应拆成几层:

CLI / IDE / Desktop UI
Local Agent Harness
规划、上下文、Tool Call
Policy Broker
判断路径、网络和审批规则
Sandbox Launcher
建立 OS 级边界
Command Runner
启动并管理受限进程树
Git / Python / Maven / npm / Tests

Harness 可以继续以普通用户身份运行,因为它需要读取对话状态、渲染界面并调用模型。但模型生成的命令不能直接继承 Harness 的全部权限。Command Runner 是两者之间的“减压舱”:它把高层 Tool Call 翻译成一个从出生起就被限制的操作系统进程。

这种拆分还有一个安全收益。真正需要接触高权限配置的代码只集中在 Sandbox Launcher 或一次性 Setup Helper 中;日常推理循环、网络客户端和 UI 不必长期持有管理员权限。

本地客户端内部最好再把三类流量分开:

Model Traffic
Harness 与模型服务之间的推理请求
Command Traffic
Harness 向 Command Runner 发送 argv、cwd、timeout 和权限引用
Result Traffic
Runner 返回 stdout、stderr、exit code、Diff 与审批事件

模型服务的网络连接不应因为“本地命令禁网”而中断,命令进程也不应复用 Harness 的认证连接绕过网络策略。换言之,本地 Agent 不是一个进程里既做推理又随手 exec(),而更像两个安全域通过窄协议连接:外侧负责思考和交互,内侧负责执行和承受风险。

Runner 接口也应传结构化参数,而不是拼接一整段 Shell 字符串。argv=["git","status"] 能减少二次解析和转义歧义;只有确实需要 Shell 语义时才显式启动 bash -lc、PowerShell 或 cmd.exe。这不会消除命令风险,但能让策略层准确识别可执行文件、参数和工作目录。

4.2 一个 Linux 本地命令是怎样被关进沙箱的#

在 Linux 上,命令启动顺序本身就是安全设计的一部分。典型流程可以拆成:

1. 创建 User Namespace
2. 写入 UID / GID 映射
3. 创建 Mount、PID、Network Namespace
4. 将挂载传播改为 private
5. 组装只读 RootFS 与可写 Workspace
6. 挂载独立 /proc、/tmp 和必要设备
7. 把进程加入专属 cgroup
8. 完成仍需特权的挂载与 LSM 准备
9. Drop 不再需要的 Capabilities
10. 按所用 LSM 语义安装或准备策略
11. 设置 no_new_privs,并应用 Landlock 等自限规则
12. 安装 Seccomp Filter
13. 清理环境变量和非白名单 FD
14. execve() 目标命令;如适用,在此完成 LSM Domain Transition

先后顺序不能随意颠倒。例如,挂载文件系统和写入部分 Namespace 映射可能需要在权限完全丢弃前完成;Landlock 通常要求先设置 no_new_privs,某些 AppArmor / SELinux 的 exec Domain Transition 又可能受 no_new_privs 影响。因此实现必须按实际 LSM 语义安排顺序,不能把“LSM、no_new_privs、Seccomp”硬编码为所有平台通用的一条固定链。无论采用哪种组合,沙箱边界都必须在目标程序第一条用户态指令运行之前闭合。

chroot() 可以改变进程看到的根目录,但它本身并不是完整安全边界。可靠实现还需要 Mount Namespace、User Namespace、权限删除和系统调用过滤共同参与。否则,一个仍拥有高权限的进程可能重新挂载文件系统或逃出目录视图。

本地 Command Runner 创建受限进程的启动顺序

图 4:安全边界必须在目标程序第一条用户态指令前闭合,并由子进程与孙进程继承;LSM 与 no_new_privs 的具体顺序取决于实现语义。

4.3 本地真实工作区怎样挂入沙箱#

本地 Agent 必须修改用户真实项目,常见做法是只把项目目录 Bind Mount 到沙箱内:

宿主:/home/user/projects/order-service
↓ bind mount
沙箱:/workspace/order-service

沙箱内部可以形成这样的文件视图:

/
├── /usr read-only
├── /bin read-only
├── /etc read-only 或最小化版本
├── /tmp private tmpfs
├── /home/agent 临时 HOME
└── /workspace/project writable

系统工具和 SDK 可以只读暴露,工作区允许读写,其余用户目录不挂载。对于无需执行的素材目录,还可以叠加 noexec;对不应出现设备节点的挂载使用 nodev;对不需要 setuid 语义的挂载使用 nosuid

有些客户端希望在真正写入用户目录前提供“预览修改”或快速回滚,可以在真实工作区之上增加 OverlayFS:

真实项目或其快照 LowerDir
任务临时修改 UpperDir
Agent 看到的项目 MergedDir

Agent 先在 UpperDir 中修改,用户接受后再转换为 Patch 或应用到真实工作区。这样减少了模型直接污染当前目录的机会。不过,OverlayFS 解决的是写入落点,不会替代路径权限、网络隔离或进程限制。

4.4 工作区内部也需要二次分区#

“允许写项目目录”仍然过于粗糙。仓库中存在一些会影响未来执行行为的敏感位置:

.git/hooks
.git/config
.env
.npmrc
.pypirc
CI 配置
Agent 配置目录
Shell 启动脚本

例如,Agent 修改 .git/hooks/pre-commit 后,恶意命令可能在未来某次人工提交时才被触发。更细的本地文件策略可以表达:

src/、tests/ 可读写
构建输出目录 可读写
.git/objects 只读或由 Git 代理管理
.git/hooks 禁止写入
用户级配置与凭证目录 不可见

对于 Agent 提供的文件 API,不能只检查字符串前缀。更安全的方式是预先打开工作区目录,后续围绕目录文件描述符调用 openat2(),根据语义选择 RESOLVE_BENEATHRESOLVE_IN_ROOT,再叠加 RESOLVE_NO_SYMLINKSRESOLVE_NO_MAGICLINKS 等解析约束。这样路径边界由内核在真正打开文件时验证,而不是在应用层先检查、稍后再使用。

4.5 本地执行身份如何从用户权限中做减法#

本地 Agent 通常从一个权限很大的真实用户会话出发。执行前需要构造更窄的身份:

Linux
User Namespace + 非特权 UID 映射
macOS
Seatbelt Profile 限制文件与网络能力
Windows
专用 Sandbox User 或 Restricted Token
+ 文件 ACL
+ Firewall

关键不是让 Agent “换一个用户名”这么简单,而是确保文件访问检查、网络规则和子进程身份都绑定到这个受限主体上。命令再启动 pythongitnode 时,后代进程不能自动回到用户原始 Token 或宿主完整 UID。

Windows 上还可以使用 Job Object 把一组相关进程纳入同一个生命周期和资源控制单元;Linux 则主要依赖 cgroup。Job Object 或 cgroup 负责“把进程树装进同一个笼子”,Restricted Token、Namespace 和 ACL 负责“笼子里的进程拥有什么权限”,两者职责不同。11

4.6 环境变量与本地 Socket 也是权限通道#

即使文件挂载已经很干净,进程仍可能从父进程继承敏感能力:

SSH_AUTH_SOCK
GIT_ASKPASS
AWS_PROFILE
AWS_ACCESS_KEY_ID
KUBECONFIG
DOCKER_HOST
DOCKER_CONFIG
NPM_TOKEN
代理账号
数据库连接串

尤其是 SSH_AUTH_SOCK。进程不需要读取私钥文件,只要能够连接 SSH Agent 的 Unix Domain Socket,就可能请求其代为签名。Docker Socket 的风险更直接,把 /var/run/docker.sock 暴露给 Agent 通常接近于把宿主容器控制权交出去。

因此,Command Runner 应从白名单重新构造环境,而不是继承后再零散删除:

设置临时 HOME
重建 PATH
清除 Credential Helper 与 AskPass
不挂载 SSH Agent、Docker 和桌面会话 Socket
只注入任务需要的普通变量
将敏感操作交给外部 Broker

这一步经常被低估。文件路径和环境变量是两条独立的能力通道,堵住其中一条并不意味着另一条自动安全。

4.7 本地网络隔离必须覆盖公网、内网与本机服务#

用户电脑的网络面比“能否访问互联网”复杂得多。它可能同时连接:

公网
家庭或办公局域网
公司 VPN
localhost 服务
Docker Bridge
本地 Kubernetes
数据库与消息队列

Linux 可以为命令建立独立 Network Namespace,通过 veth 接入受控出口;macOS 可以用 Seatbelt 和系统网络策略;Windows 可以把 Firewall 规则绑定到专用 Sandbox User。无论平台如何实现,网络策略都应作用于整棵子进程树,而不是只禁止 Harness 的 HTTP 工具。

还要注意 localhost 的含义取决于隔离方式:

独立 Network Namespace:
127.0.0.1 指向沙箱自己的回环接口
共享宿主网络:
127.0.0.1 可能直接命中用户电脑上的数据库或调试服务

IP 防火墙也不会自动控制 Unix Domain Socket。对于 Docker、SSH Agent、桌面总线和本地代理等能力,必须从文件挂载和 Socket 权限层面单独阻断。

需要联网安装依赖时,较稳妥的做法是只给本次命令开放包仓库或 Git 目标,而不是临时放开整个公网。Approval 产生的应该是一项短期网络能力,而不是全局关闭沙箱。

网络“已开启”和“域名规则已强制执行”也应分开理解。允许进程联网,只代表路由层存在出口;要让 pypi.org=allow、其他域名拒绝这样的策略生效,还需要确保命令无法绕过代理直连。一个常见错误是配置了域名列表,却没有启动强制代理,此时策略文件看起来很精细,实际 Socket 仍可直接出网。

本地代理还应默认保护私有目标。域名最终解析到 127.0.0.1、RFC1918、VPN 网段或链路本地地址时,应再次校验;对于 Unix Socket,也要建立独立 Allowlist。因为 /var/run/docker.sock 并不是“一个文件”,而是一条能够命令宿主 Docker Daemon 的控制通道。

4.8 进程树、PTY、超时与强制回收#

一条表面简单的命令可能展开成很深的进程树:

bash
└── mvn
└── JVM
├── compiler
└── test worker

或:

npm
└── node
└── child_process
└── background server

所有后代必须继承相同的文件、网络、身份、Seccomp 和资源边界。任务取消时不能只终止父 Shell。Linux 上可以先发送 SIGTERM,等待 Grace Period,再使用 SIGKILLcgroup.kill 清理整个 cgroup;Windows 可调用 TerminateJobObject() 终止整个 Job,也可设置 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,在最后一个 Job Handle 关闭时终止成员进程。安全实现还应禁止 Breakaway,并在目标进程开始执行前将其纳入 Job。

执行通道通常有两种:

普通 Exec
适合编译、测试、脚本和批处理
PTY
适合 Shell、REPL、调试器和需要 TTY 的 CLI

PTY 需要管理 master/slave、前台进程组、窗口尺寸和终端信号。它改善交互兼容性,但不会自动带来更强隔离,真正的安全边界仍由启动 PTY 内命令时使用的身份和 OS 策略决定。

4.9 Local 与 Worktree 解决的是不同问题#

本地 Agent 可以直接修改当前项目,也可以创建独立 Git Worktree:

Repository
├── 用户当前 Worktree
└── Agent 独立 Worktree

Worktree 能隔离并发修改、分支和未提交文件冲突,却不能阻止进程读取用户主目录、访问 VPN 或创建大量子进程。因此:

Worktree 是代码状态隔离;OS Sandbox 是进程能力隔离。

Codex 当前也把 Local、Worktree 和 Cloud 作为不同环境模式,其中 Local 与 Worktree 都在用户设备上执行。2

4.10 本地命令的完整技术链路#

把前面的技术拼起来,一条本地测试命令会经过:

模型生成 Tool Call
Harness 解析 argv、cwd 与所需能力
Policy Broker 判断文件、网络与审批规则
Launcher 构造 Namespace / Token / ACL / cgroup
Command Runner 清理环境和本地 Socket
execve / CreateProcess 启动受限命令
所有后代继承同一边界
stdout、stderr、exit code 流回 Harness
超时或取消时回收整个进程树

stdout 与 stderr 应使用有界缓冲和分块事件。测试日志过快时,需要背压、截断或落盘;构建产物和二进制文件应走独立 Artifact 通道,不能塞进文本流中。

本地 Agent 沙箱从任务与策略检查到受限执行、工作区修改和结果返回的完整链路

图 5:本地执行以真实工作区为目标,但文件身份、网络、进程树和结果回传仍分别经过约束与审计。


五、Web 云端沙箱的技术实现#

Web 云端模式并不是把本地沙箱原封不动搬到服务器上。它需要把浏览器控制界面、任务服务、Agent Harness 与真正执行不可信命令的环境拆开,并为每个任务构造一套独立的文件、身份、网络和内核边界。

5.1 浏览器只属于控制平面#

典型结构是:

Browser / Web UI
↓ HTTPS
Task API / Session Service
Cloud Agent Harness
↓ Control RPC
Sandbox Command Runner
Shell / Git / Compiler / Tests

浏览器负责提交任务、显示事件、处理 Approval 和审查结果。Shell、编译器和测试程序位于远端执行平面。即使任务由桌面客户端发起,只要选择 Cloud 模式,它也不会自然继承本机文件、浏览器登录态或 VPN 权限。

控制平面和执行平面之间通常至少存在三类通道:

Command Channel
下发 argv、cwd、环境引用和超时
Event Channel
返回 stdout、stderr、状态和工具事件
Artifact Channel
传输 Diff、报告和二进制制品

把三者分开,可以避免大文件阻塞命令控制,也能让文本事件流维持清晰的背压语义。

Web 云端 Agent 的控制平面、执行平面及命令与事件通道

图 6:浏览器属于可重连控制平面,Shell、Git 和编译器运行在服务器端执行平面;图中上层箭头表示概念职责流,不要求所有实现严格串行。

5.2 云端 Sandbox 从什么状态启动#

一个任务环境通常不是从宿主根文件系统直接复制出来,而是由几个独立层拼成:

只读 Base Image 或 VM Snapshot
+
任务独立 Writable Layer
+
Repository Workspace
+
Private /tmp
+
按需只读 Cache

启动过程可以概括为:

1. 创建任务身份与资源组
2. 准备 RootFS 或恢复 VM Snapshot
3. 创建任务独立写层
4. 建立 PID、Mount、Network 边界
5. 挂载 Workspace 与必要 Cache
6. 启动沙箱内 Init / Guest Agent
7. 建立控制通道
8. Clone 仓库并 Checkout 指定版本
9. 进入 Setup Phase

云端 Workspace 应来自 Clone、上传或受控存储,而不是把宿主机某个真实目录直接作为可写 Volume 暴露给不可信任务。即使多个任务复用依赖缓存,缓存也更适合只读挂载,任务写入应落在自己的 Copy-on-Write 层。

从启动状态看,云端 Sandbox 通常存在三种来源:

Cold Image
从基础镜像完整创建,状态最干净,启动最慢
Prepared Template
已包含语言运行时和常用工具,但没有具体仓库数据
Cached / Snapshot State
复用已安装依赖或已初始化运行时,再为当前任务切出私有写层

复用不能破坏隔离。缓存内容应可验证、可失效,并与用户 Secret、仓库工作区和任务身份分开。恢复一个缓存容器时,最危险的不是依赖版本旧,而是上一个任务的凭证文件、后台进程或可写缓存被悄悄带进来。因此“缓存命中”仍要经过工作区重置、身份重建、进程清空和权限重新应用。

5.3 RootFS、OverlayFS 与任务写层#

容器型沙箱常使用 OverlayFS:

LowerDir 只读镜像层
UpperDir 当前任务修改
WorkDir OverlayFS 工作目录
MergedDir Agent 看到的根文件系统

Agent 安装依赖或修改系统层文件时,变化进入 UpperDir,不会污染 Base Image。任务完成后,平台可以删除 UpperDir,或只从 Workspace 中导出 Git Diff 和 Artifact。

MicroVM 则可能使用 Copy-on-Write 块设备:基础磁盘镜像只读共享,每个任务拥有独立增量块。两种方案的共同原则是:

基础环境可以共享,任务写入必须私有。

/tmp、运行时缓存和进程 PID 文件也应属于任务自己的临时空间。否则,即使源码目录彼此隔离,任务仍可能通过共享临时目录互相观察或污染。

5.4 容器、gVisor 与 MicroVM 在执行面中如何工作#

云端 Sandbox 的运行时区别,核心在于不可信程序的系统调用最终落在哪里。

普通容器#

Agent Process
↓ syscall
Host Linux Kernel

Namespace、cgroups、Capabilities、Seccomp 和 LSM 都由宿主内核执行。用户空间视图彼此隔离,但所有任务共享同一份内核代码。

gVisor#

Agent Process
↓ syscall
gVisor Sentry
↓ limited host syscall
Host Kernel

Sentry 在用户态重新实现大量 Linux 系统调用、进程、内存、信号、文件和网络语义。不可信程序的 syscall 先由 Sentry 处理,而不是一比一进入宿主内核。外部文件访问还可以通过 Gofer 代理。12

MicroVM#

Agent Process
↓ syscall
Guest Linux Kernel
↓ virtio / VMM
KVM / Host Kernel

任务拥有独立 Guest Kernel。宿主侧通常通过虚拟网卡、virtio 块设备和 vsock 与 Guest Agent 通信。Firecracker 使用精简设备模型和 KVM 建立轻量虚拟机边界,并可以配合 Jailer 再限制 VMM 进程本身。13

三条路径可以压缩成:

Container:Application → Host Kernel
gVisor:Application → User-space Kernel → Host Kernel
MicroVM:Application → Guest Kernel → VMM → Host Kernel

这里不是在做产品选型,而是在解释云端命令的内核路径:后两种路径减少了任务直接接触宿主内核接口的机会。不过隔离强度不是简单的线性排序,仍取决于具体配置、暴露接口与威胁模型。

普通容器、gVisor 与 MicroVM 的系统调用隔离路径对比

图 7:普通容器共享宿主内核,gVisor 在用户态截获大量系统调用;MicroVM 通过 Guest Kernel 与虚拟化边界隔离。Firecracker 是使用 KVM 的 VMM,而不是 KVM 本身。

5.5 Harness 放在沙箱内还是沙箱外#

云端 Agent 常见两种结构。

结构 A:Harness 在沙箱外#

Agent Harness
↓ RPC
Sandbox Runner
Command Process

模型客户端、任务状态和权限判断留在控制环境中,沙箱只承载命令执行。优点是长期凭证和控制逻辑不必进入不可信环境,沙箱网络也可以更窄;代价是文件、PTY 和事件都要通过远程协议代理。

结构 B:Harness 与工具都在沙箱内#

Sandbox
├── Agent Harness
├── Shell
├── Git
└── Tests

优点是本地 IPC 和文件操作简单,Agent 可以直接调用工具;代价是模型访问凭证、任务控制信息和更复杂网络能力可能进入同一边界。

很多系统会采用折中设计:推理编排在外,轻量 Tool Runtime 或 Runner 在内。无论选择哪种结构,最终执行命令的子进程都必须属于沙箱自己的身份、cgroup 和网络边界。

5.6 Setup Phase 与 Agent Phase 怎样完成权限单向收缩#

云端环境往往需要联网下载依赖,但真正由模型生成的命令不应长期拥有同样权限。于是任务被拆成两个阶段。

Setup Phase#

运行预先配置的 Setup Script
访问允许的包仓库
使用安装所需的临时 Secret
构建依赖与缓存

权限交接#

撤销 Setup Secret
终止 Setup 进程树
清理环境变量与临时凭证文件
收紧 Egress Policy
Drop 额外 Capabilities
固定挂载边界
启动 Agent Runner

Agent Phase#

执行模型动态生成的命令
默认离线或仅访问白名单
只使用任务级短期能力
持续产生审计与事件流

这道交接必须具有单向性。Agent Phase 不应能够重新启动拥有 Setup 身份的进程,Setup Token 也不应留在磁盘、环境变量、Shell History 或后台守护进程中。

Codex Cloud 的公开模型正是 Setup 阶段可联网安装依赖,Agent 阶段默认关闭互联网;配置为 Secret 的值在 Agent 阶段开始前被移除。14 15

这里还要区分普通 Environment Variable 和 Secret。普通环境变量可能贯穿整个任务,用于 JAVA_HOME、运行模式或非敏感配置;Secret 则只在 Setup 执行时解密和注入,并在 Agent Phase 前撤销。两者如果共用同一个注入通道,开发者很容易把长期凭证误当成普通变量留给动态命令。

Setup 与 Agent 最好使用不同 Shell 会话甚至不同进程树。这样 Setup 中临时执行的 export TOKEN=... 不会自然继承到 Agent;若确实要持久化非敏感变量,应通过显式环境配置或受控启动文件完成,而不是依赖某个尚未退出的父 Shell。

Setup Phase 经单向权限收缩闸门进入 Agent Phase,移除 Secret、限制网络、降低权限并冻结挂载策略

图 8:依赖安装完成后,平台移除 Setup Secret、结束准备阶段,并以更低权限启动动态 Agent 执行阶段。

5.7 云端网络是怎样被迫经过 Egress Proxy 的#

仅在容器内设置 HTTP_PROXY 仍然可以被绕过。可靠方案应从路由层保证 Sandbox 没有直接公网出口,只能连接受控代理:

Sandbox netns / VM NIC
Default Deny Route / Firewall
↓ only allowed path
Egress Proxy
DNS、目标 IP、域名与协议校验
Internet

代理至少需要处理四次校验:

请求域名是否允许
DNS 最终 IP 是否属于禁止网段
TCP 实际连接目标是否与解析结果一致
HTTP 重定向后的新目标是否仍然允许

默认禁止的目标通常包括:

127.0.0.0/8
RFC1918 私网
宿主网关
云元数据服务
Kubernetes API
控制面服务
其他任务网段
内部数据库和消息队列

若代理只允许 HTTPS CONNECT 而不终止 TLS,它通常只能验证连接目标,无法可靠检查加密后的 HTTP 方法与路径。要实施细粒度 HTTP 方法限制,需要由应用层代理终止 TLS,或让任务使用平台提供的受控 HTTP 工具。网络策略能够看见什么,取决于代理位于协议栈的哪一层。

Codex Cloud 当前在 Agent 阶段默认关闭互联网;开启后可以限制域名和 HTTP 方法,外部 HTTP/HTTPS 流量通过受控代理。16 14

5.8 任务身份与 Credential Broker#

云端平台可能拥有 Git、包仓库、对象存储和企业系统的长期身份,但这些身份不能复制进任务环境。更合理的链路是:

Sandbox 发出语义操作请求
Credential Broker / Git Proxy / MCP Gateway
校验 user_id、task_id、sandbox_id
校验 repository、branch、operation
签发短期 Token 或在代理外部完成操作

能力可以被压缩到:

主体:sandbox-123
资源:org/order-service
分支:agent/fix-timeout
操作:push
有效期:60 秒
最大次数:1

如果采用代理执行,真实凭证从头到尾都不会进入沙箱;如果必须签发 Token,也应尽量缩短有效期、Scope 和使用次数。任务结束或沙箱身份变化时,Broker 必须立即撤销关联能力。

5.9 云端 Command Runner 与事件流#

Command Runner 通常通过 gRPC、Unix Socket 或 VM vsock 接收执行请求:

{
"argv": ["mvn", "test"],
"cwd": "/workspace/order-service",
"timeoutSeconds": 300,
"envRefs": ["JAVA_HOME"],
"networkCapability": "maven-central-only"
}

Runner 在沙箱内创建进程组,捕获 stdout、stderr 和 exit code,并把执行状态转换成事件:

command.accepted
command.started
stdout.delta
stderr.delta
process.spawned
command.completed
command.killed

事件通道必须有背压。若浏览器或任务服务消费速度下降,Runner 不能无限占用内存。可采用有界 Ring Buffer、批量发送、日志截断和落盘;断线恢复时从持久化事件偏移继续读取。大体积测试报告和二进制构建产物进入 Artifact Store,事件流只携带引用。

为了支持 Web 页面刷新、移动端接管和长任务继续运行,事件不能只依赖一条活着的 WebSocket。更稳妥的语义是:Runner 把事件提交到任务级日志,Gateway 再按 task_id + sequence 向不同客户端投递。浏览器掉线只丢失实时连接,不会中断沙箱,也不会改变命令执行事实。

Sandbox Runner
↓ append
Durable Task Event Log
↓ subscribe from sequence N
SSE / WebSocket Gateway
Desktop / Web / Mobile

控制命令也要幂等。例如用户连续点击两次“停止”,控制面可以携带同一个 request_id;Runner 只执行一次终止,并返回同一最终状态,避免重复清理和状态机乱跳。

5.10 任务结束时怎样真正清空环境#

云端沙箱的可销毁性来自明确的资源边界。任务结束时应按顺序执行:

停止接受新命令
撤销任务 Credential
终止完整进程树或销毁 VM
关闭控制与网络通道
导出 Diff、日志和 Artifact
卸载 Workspace 与临时挂载
删除任务 UpperDir 或增量磁盘
释放任务身份与网络地址

只删除工作目录并不够。后台进程、挂载点、网络连接和短期身份都可能继续存活。容器环境应清理整个 cgroup 与 Namespace;MicroVM 环境可以直接销毁 Guest,将进程和内核状态一起抹去。

平台若要保存状态,也应显式区分:

Agent Checkpoint
messages、tool calls、plan、observation
Sandbox Snapshot
filesystem、memory、process、runtime state

逻辑状态和操作系统状态不是同一回事,不能只保存对话 Checkpoint 就假设编译环境能够原地恢复。

5.11 Web 云端任务的完整技术链路#

用户在 Web 页面提交任务
Task Service 创建任务身份
准备容器 RootFS 或恢复 MicroVM Snapshot
建立文件、cgroup、网络和内核边界
Clone 仓库并挂载任务写层
Setup Phase 安装依赖
撤销 Setup Secret 并收紧网络
启动 Agent Harness / Sandbox Runner
执行修改、编译和测试
事件经 SSE / WebSocket 返回页面
Diff、报告和制品进入 Artifact 通道
撤销身份与 Secret,回收环境或按隔离策略缓存 / 快照

Web 页面只是远程观察窗。真正的云端沙箱,是一套围绕任务身份重新组装出来的临时操作系统边界。

云端 Sandbox 从创建任务、克隆仓库、Setup、移除 Secret、执行 Agent,到返回结果并销毁或缓存环境的完整生命周期

图 9:云端任务在受控网络、临时身份和独立工作区内完成;结果被导出后,环境按策略销毁或进入受限缓存。


六、大厂案例:OpenAI Codex 的本地与云端双形态 Sandbox#

以下产品能力核对截至 2026-09-04。

这一章保留 Codex 作为技术案例,但需要先把产品层和执行层分清楚。OpenAI 当前的双端形态实际上跨越两层:用户在 ChatGPT 桌面端、Web 或移动端看到的是 ChatGPT Work 与 Codex 等 Agent 入口;真正承担代码、Shell 和文件执行的,则是本地 Codex Sandbox 或云端 Codex Harness。

官方资料给出的边界非常明确:ChatGPT Work Local 通过桌面应用在用户设备上运行,可以使用经批准的本地文件、应用和浏览器会话;Work Cloud 则在 OpenAI 管理基础设施的隔离环境中运行 Codex Harness,并可在 Web、移动端和桌面端之间同步与继续。17 18 19

因此,这个案例可以从两个视角理解:

产品视角:ChatGPT 的 Local Work 与 Cloud Work
执行视角:Codex Local Sandbox 与 Codex Cloud Sandbox

前者告诉用户“任务在哪里运行、能使用哪些资源”;后者揭示命令最终怎样被操作系统约束。两者叠在一起,才是完整的双端沙箱。

还要特别避免一个误解:本地任务在设备上执行工具,不表示模型推理也在设备上离线完成。ChatGPT Work Local 可以保留文件在本机,但完成任务所需的文件片段、提示、截图、浏览器内容或工具结果仍可能发送到 OpenAI 服务;同样,在桌面应用中打开一个 Cloud Task,也不会把它迁移到本机。18

6.1 Codex Local:上层策略如何映射到不同操作系统#

Codex Local 的核心不是“在本地直接跑命令”,而是把用户已有的一大团权限拆成多个互不等价的能力面。ChatGPT 桌面端可能同时拥有以下工具:

Local Shell / Code Tool
运行 Git、Python、npm、Maven 和测试进程
Built-in Browser / Browser Extension
读取网页、现有标签页或单独浏览器 Profile
Computer Use
截图、点击、输入和操作桌面应用
Connected Apps / Plugins / MCP
通过各自的身份和传输访问外部系统

这些能力并不共享一个万能沙箱。OpenAI 的本地安全说明明确把文件访问、Computer Use、浏览器和连接应用视为不同权限与审批面;Codex 的公开 Agent Loop 也指出,Codex 提供的 Sandbox 描述只约束内置 Shell Tool,MCP Server 需要为自己的执行和数据访问承担独立 Guardrail。18 20

这意味着,设计 Agent 安全时不能说“Shell 已经被沙箱,所以整个 ChatGPT 桌面端都安全了”。更准确的结构是:

ChatGPT Desktop Agent
├── Shell Sandbox Policy
├── Browser Profile / Site Approval
├── Computer Use OS Permission
├── Connector Account Scope
└── MCP Server Guardrail

每条工具通道都拥有自己的身份、数据源和越权方式。

第一层:Local、Worktree 与 Cloud 是执行位置,不是聊天界面。

在 ChatGPT 桌面应用中选择 Codex 后,Local 直接工作于当前项目目录,Worktree 在本机创建独立 Git Worktree,Cloud 则运行在远端环境。Local 与 Worktree 都在用户电脑上执行。2 21

Local
当前真实工作区 + OS Sandbox
Worktree
独立 Git 工作树 + OS Sandbox
Cloud
远端工作副本 + Cloud Sandbox

Worktree 只隔离 Git 修改,不隔离操作系统权限。它可以避免两个 Agent 同时改坏同一 Checkout,却不能阻止命令读取 ~/.ssh、连接公司 VPN 或访问 Docker Socket。因此 Worktree 和 OS Sandbox 必须叠加,而不能互相替代。

第二层:Permission Profile 把文件与网络规则编译成一次本地执行边界。

Codex 当前提供 :read-only:workspace:danger-full-access 等内置权限 Profile,也允许自定义文件路径和网络目标。:workspace 的语义是允许在活动 Workspace Root 和系统临时目录中写入,而不是自动授予整个用户目录。自定义 Profile 还可以在可写工作区内部继续拒绝读取 .env 等敏感文件。22

[features]
network_proxy = true
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
"**/*.env" = "deny"
[permissions.project-edit.network]
enabled = true
[permissions.project-edit.network.domains]
"pypi.org" = "allow"
"*.pythonhosted.org" = "allow"

这里有一个非常关键的双开关:

network.enabled = true
只表示命令允许联网
features.network_proxy = true
才让域名 Allowlist 通过代理被强制执行

如果网络开启但代理未启用,命令可能获得直接且不受域名列表约束的网络连接。反过来,当代理真正生效时,本地和私有网络目标默认会受到额外保护,用来抵御 DNS Rebinding 和意外访问 localhost、VPN 网段或内网服务。Unix Socket 又是另一条独立路径,需要单独配置。22

第三层:模型知道权限,但真正强制权限的是操作系统。

Codex 在构造 Agent 输入时,会注入一段描述 Sandbox、网络、可写目录和审批时机的 Developer Message,让模型提前知道边界,从而减少无意义的越权尝试。20

Model Prompt
“当前只允许写 Workspace,网络关闭”
模型尽量生成边界内命令
Policy / Approval 再判断请求
OS Sandbox 最终强制执行

这是一种“认知约束 + 技术约束”的双层设计。Prompt 让模型更聪明地使用权限,但 Prompt Injection 可能影响模型判断,所以安全性不能依赖模型是否听话。Seatbelt、Bubblewrap、Seccomp、ACL 与 Firewall 才是最后的硬门。

第四层:同一个上层 Profile 映射为不同平台原语。

当前跨平台路径可以概括为:

macOS
Seatbelt Profile + sandbox-exec
Linux / WSL2
Bubblewrap + User / Mount / PID Namespace + Seccomp
兼容路径可使用 Landlock
Native Windows
elevated:Sandbox User + Restricted Token + ACL + Firewall
unelevated:当前用户派生的 Restricted Token + ACL + 环境级离线控制

在 macOS 上,Codex 根据选择的权限模式生成 Seatbelt Profile;如果所选策略无法被平台沙箱可靠执行,当前文档描述的原则是拒绝运行,而不是静默降级成无沙箱命令。在 Linux 与 WSL2 上,bwrap 负责重新组装文件和进程视图,Seccomp 缩小 syscall 面,User Namespace 提供非特权身份边界。Windows 的 elevated 模式使用专用低权限用户和用户级 Firewall,unelevated 模式则从当前用户派生 Restricted Token,并以环境级离线控制替代专用 Offline User 的防火墙规则,隔离强度更弱。1 22 15 23

第五层:浏览器与 Computer Use 不是 Shell Sandbox 的附属功能。

ChatGPT Work Local 的内置浏览器使用与用户常规浏览器分开的 Profile;Chrome 扩展在获批后可以接触现有标签页和登录状态;Computer Use 则依赖操作系统的屏幕录制、辅助功能和应用审批。在 macOS 上,屏幕可见性和点击能力分别依赖 Screen Recording 与 Accessibility 权限;在 Windows 上,Computer Use 操作的是当前可见桌面。18

因此,一次本地任务可能同时跨过几道边界:

读取项目文件 → Shell / Filesystem Policy
运行测试 → OS Sandbox + Process Tree
打开内网页面 → Browser Approval + Existing Account
点击桌面应用 → Computer Use + OS Permission
调用 CRM Connector → Connected Account Scope
调用 MCP Tool → MCP Server 自身 Guardrail

这也是 ChatGPT 本地端比传统“命令行沙箱”更复杂的原因。它不是只有一只被关在笼子里的 Shell,而是一组各自带锁的工具舱。

一条完整的 ChatGPT / Codex Local 执行链可以写成:

用户在 ChatGPT Desktop 选择 Local 或 Worktree
桌面端确定 Workspace Root 与可用工具
Permission Profile 编译文件、网络和拒绝规则
Sandbox 描述进入 Agent Context
模型生成 Shell Tool Call
Policy 判断 Allow / Deny / Ask
平台 Command Runner 创建 OS Sandbox
Git / Python / npm / Tests 在受限进程树中运行
stdout、stderr、Diff 与审批事件返回桌面端
本地文件保留,必要上下文发送给云端模型继续推理

Codex Local 跨平台权限映射概念图;Windows 栏展示的是可能原语的合集

图 10:同一组上层权限语义会映射为不同操作系统原语。Windows 栏是概念性原语合集:Sandbox User 与用户级 Firewall 仅属于 elevated 模式;unelevated 使用当前用户派生的 Restricted Token、ACL 与较弱的环境级离线控制。图中的 Job Object 是 Windows 通用的可选进程树原语,并非当前公开资料已确认的 Codex 组件。

6.2 Codex Windows:为什么需要多层原语拼成一个沙箱#

Windows 案例最有技术价值,因为它展示了:当操作系统没有一个与 Linux Namespace 或 macOS Seatbelt 完全同构的“开放式 Agent Sandbox”时,如何把身份、文件 ACL、Restricted Token、Firewall 和多个专用进程拼成一条可用边界。23 24

以下重点讨论官方 Windows 文档中的 Elevated 主路径;当前也存在不需要管理员初始化、但隔离更弱的 Unelevated Fallback,两者不能视为同一安全强度。

OpenAI 需要满足的不是“运行一款固定应用”,而是允许 Agent 动态驱动:

PowerShell
Git
Python
Node.js
包管理器
编译器
任意项目测试程序

这些工具的系统调用和文件需求无法在安装时完全枚举,因此很多经典 Windows 隔离方案都不合身。

为什么没有直接使用 AppContainer、Windows Sandbox 或低完整性级别。

AppContainer 的 Capability 模型适合提前知道资源需求的窄应用,但 Coding Agent 会动态调用大量传统 Win32 程序,能力面过于开放且变化频繁。Windows Sandbox 提供更强的轻量 VM 隔离,却位于独立、可销毁的桌面中,不适合直接操作用户真实 Checkout、现有工具和开发环境,而且并非所有 Windows 版本都可用。Mandatory Integrity Control 可以让低完整性进程无法写中完整性对象,但把真实工作区标记为低完整性会改变该目录对整台主机的信任语义,影响范围不只限于 Codex。24

于是工程问题被拆成两个更具体的目标:

文件目标:命令可以读到开发环境,但只能写显式 Workspace
网络目标:Offline 命令及其所有后代都不能直接出站

Synthetic SID 与 Write-restricted Token#

Windows 使用 SID 表达安全主体,使用 ACL 表达对象允许哪些主体读、写和执行。Synthetic SID 不对应真实用户,却可以进入 ACL,所以 Codex 创建了一个专用于写边界的 sandbox-write SID。

真实命令通过 Write-restricted Token 启动。Windows 对写操作进行两次访问检查:

检查一:Sandbox User Token 的正常 SID 是否允许写入
检查二:Restricted SID 列表中是否也有主体被允许写入
两次都通过
写成功

Sandbox Setup 把 sandbox-write 的写、执行和删除权限授予当前目录以及额外 writable_roots,同时在 <cwd>/.git<cwd>/.codex<cwd>/.agents 等“可写目录中的只读岛屿”上设置显式拒绝。最终命令的 Restricted SID 列表包含 Everyone、Sandbox Logon SID 和 sandbox-write24

这个设计有一个很漂亮的性质:Sandbox User Token 的普通 SID 先限定其基础权限,Restricted SID 再把写能力压缩到沙箱允许的位置。两次检查都要通过,因此它是权限求交,而不是权限替换。

为什么无效代理变量不能真正断网#

在不提权的前提下,Codex 很难安装强制 Windows Firewall 规则,于是早期方案通过环境变量和 PATH 污染常见联网工具:

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
GIT_SSH_COMMAND=cmd /c exit 1
PATH 前置 denybin

这会让 Git、包管理器和遵守代理变量的工具失败,但自定义程序可以忽略环境、绕过 PATH,直接调用 Winsock 创建 Socket。也就是说,它对“普通工具误联网”有效,对“任意代码强制禁网”无效。24

更棘手的是,Firewall 也不能简单绑定到 codex.exe。真正出网的是 Codex 启动的 git.exepython.exenode.exe;按程序路径阻止 python.exe 又会误伤用户自己启动的 Python。Firewall 需要识别“这一棵由 Agent 启动的进程树”,而 Restricted Token 中的 Synthetic SID 并不是可直接匹配的 Principal。

Online 与 Offline Sandbox User#

最终方案在一次性设置阶段请求管理员权限,创建两个低权限本地用户:

CodexSandboxOffline
被用户级 Firewall 规则阻止全部出站连接
CodexSandboxOnline
不受 Offline Firewall 规则约束

命令以其中一个 Sandbox User 作为真正 Principal 启动。此后 Git、Python、Node 和测试 Worker 都继承该用户身份,Firewall 因而能够覆盖整棵后代进程树,而不会影响真实用户会话中的同名程序。

Setup Helper 需要完成:

创建 sandbox-write Synthetic SID
创建 Online / Offline Sandbox User
使用 DPAPI 加密并保存用户凭证
保证 Sandbox User 无法读取自身保存的凭证
创建并校验 Offline 用户的出站 Firewall 规则
向必要系统目录和用户目录补充只读 ACL

由于 Sandbox User 默认不能读取另一个用户的 Profile,Codex 还需要向 C:\Users\<real-user>C:\WindowsProgram FilesProgramData 等常用路径补充最佳努力的读权限。大规模 ACL 应用成本较高,因此部分工作被异步执行,避免每次启动都卡在安全配置上。24

Setup Helper 与 Command Runner 分离#

直觉上的调用是:

codex.exe
→ LogonUserW(sandbox user)
→ CreateRestrictedToken(...)
→ CreateProcessAsUserW(child)

但从真实用户一侧跨到另一个用户并使用受限 Token 启动最终子进程,会撞上 Windows 的权限边界。解决方案是先让一个 Runner 真正运行在 Sandbox User 一侧,再由它创建最终 Restricted Token:

codex.exe(真实用户、非提权)
↓ CreateProcessWithLogonW
codex-command-runner.exe(Sandbox User)
↓ OpenProcessToken
↓ GetTokenInformation,取得 Sandbox Logon SID
↓ CreateRestrictedToken
↓ CreateProcessAsUserW
Child Process(Sandbox User + Write-restricted Token)

最终形成四层:

codex.exe
负责 Harness、对话与策略
codex-windows-sandbox-setup.exe
负责 UAC 后的用户、ACL、DPAPI 与 Firewall 初始化
codex-command-runner.exe
负责在 Sandbox User 一侧铸造受限 Token
Child Process
真正运行 PowerShell、Git、Python 和测试

这种拆分缩小了高权限代码面。日常 codex.exe 始终保持普通用户身份,只有 Setup Helper 在需要时跨越 UAC;动态命令永远由低权限 Runner 启动。在当前公开实现中,两种 Windows 沙箱模式默认启用 Private Desktop(可关闭),用来进一步隔离图形对象、窗口和交互桌面。23 24

Windows 案例最值得记住的不是某个 API 名称,而是一个设计原则:

要让网络和文件策略覆盖任意后代程序,必须给整棵进程树一个操作系统能够稳定识别的受限身份。

Codex Windows elevated 模式的 Setup 组件与日常运行组件合成图

图 11:这是“一次性 Setup 组件 + 日常 Runtime 组件”的合成视图,不是一条每次都完整执行的时序链;日常路径是 codex.exe → CreateProcessWithLogonW → command-runner → Restricted Token → Child,不会重复经过 UAC Setup Helper。图中的“Sandbox 容器”仅表示边界组合,不是 Windows 容器;写入检查的普通 SID 属于 Sandbox User Token,第二重检查使用 Restricted SID;Online User 只是不命中 Offline User 的出站 Firewall 规则,并不自动等于受控联网。

6.3 Work Cloud 与 Codex Cloud:并列的托管执行路径#

在用户视角下,ChatGPT Work Cloud 可以从 Web、移动端或桌面端发起,并在用户离开当前界面后继续执行;但任务始终运行在 OpenAI 管理基础设施上,不会因为用户从桌面应用打开它就获得本地文件、已安装应用、浏览器会话或私有网络权限。17 19 25

公开资料需要把两条路径并列区分:ChatGPT Work Cloud 当前运行在 VM-backed Sandbox 中,可以跨任务复用或替换环境并保留符合条件的状态;Codex Cloud 编码聊天则会创建或恢复隔离 Container,组织仓库、Setup 和 Agent Phase。两者共享核心 Harness 与部分隔离机制,但工具、权限和管理控制并不完全相同;公开资料没有说明 Codex Container 嵌套在 Work Cloud VM 内。并非所有 Work Cloud 任务都绑定 Git 仓库,也不都遵循 Codex Cloud 的容器生命周期。19 14

从架构上看,这意味着 Cloud Task 的生命周期必须归属于服务端任务身份,而不是归属于某条浏览器连接:

Browser / Desktop / Mobile
↓ submit / subscribe
Cloud Task Service
↓ owns lifecycle
Isolated Execution Environment

这是根据“任务可跨端同步并在用户离开后继续”的产品行为作出的架构推断:客户端是可重连控制器,Sandbox 才是持续运行的执行实体。

第一层:Work Cloud 与本地设备之间存在天然断点。

云任务不会自动继承:

本地文件与未上传修改
桌面应用
本地浏览器 Profile、标签页和登录态
密码管理器与浏览历史
公司 VPN 与私有网络路由
本地 SSH Agent、Docker Socket 和环境变量

需要使用的数据必须通过上传、Project、Library、连接应用或受控仓库进入云端。连接应用使用的是已授权账号的权限,它提供一条服务侧访问路径,但不会把整台设备或 VPN 网络挂进 Sandbox。19

第二层:Cloud Browser、Web Search、Connector 与 Shell Network 是四套不同通道。

ChatGPT Work Cloud 中,Hosted Browser 不继承用户本地浏览器的 Cookie、标签页、密码和历史;如需登录,使用单独的托管登录流程。更重要的是,关闭 Code/Shell 的公网访问,并不会自动关闭 Cloud Browser、Web Search 或 Connected Apps。它们各自有独立权限与网络面。19

Cloud Browser
托管浏览器 Profile + 网站审批
Web Search
托管搜索服务与结果策略
Connected Apps
服务侧连接 + 授权账号 Scope
Work Cloud Code / Shell Network
独立网络策略 + Managed Destination Allowlist

这和本地端的多工具边界形成呼应:一个 Agent 可以同时拥有多条“看起来都能访问互联网”的通道,但每条通道的身份、协议和审计点不同。只关掉其中一条,不等于 Agent 完全离线。

第三层:Codex Cloud 环境从容器、仓库和 Setup 开始。

下面的流程特指 Codex Cloud 编码任务,而不是所有 Work Cloud 任务:

官方公开流程是:

1. 创建隔离 Container
2. Checkout 选定 Repository、Branch 或 Commit SHA
3. 执行 Setup Script
4. 缓存恢复时可执行 Maintenance Script
5. 应用 Agent Internet Access 设置
6. Agent 循环运行终端命令、编辑代码和校验结果
7. 返回最终回答与文件 Diff

默认 universal 镜像预装常用语言、包和工具,环境还可以固定 Python、Node.js 等版本。14

Cloud Environment 中的普通 Environment Variable 和 Secret 有不同生命周期:

Environment Variable
Setup 与 Agent Phase 都可用
Secret
仅在任务执行时解密
只提供给 Setup Script
Agent Phase 开始前移除

Setup Script 在独立 Bash Session 中运行,因此普通 export 不会自动持久化到 Agent Phase。这个细节防止 Setup Shell 变成一条隐蔽的父进程继承链;确实需要长期存在的非敏感变量,应通过环境配置显式声明。14

第四层:权限在 Setup 与 Agent 之间单向收缩。

Setup Phase
可联网安装依赖
可使用 Setup Secret
执行预配置脚本
Security Handoff
删除 Secret
结束 Setup Shell
应用 Agent 网络策略
Agent Phase
运行模型动态命令
默认关闭公网
必要时只开放受控目标

Agent 阶段默认无互联网;开启后,可以使用域名 Allowlist 和 HTTP Method 限制。官方文档还允许把请求收紧为 GETHEADOPTIONS,阻止 POSTPUTPATCHDELETE 等更容易产生外部副作用或数据外传的方法。所有外部 HTTP/HTTPS 流量经过平台代理。16 14

HTTP Method 限制并不是万能 DLP。敏感数据仍可能被编码进 URL Query、请求头或允许的下载协议中,但它能明显缩小“把仓库内容直接 POST 到外部站点”的通道。真正的保护仍来自默认断网、窄域名列表、无长期 Secret 和结果审查的组合。

第五层:容器缓存加速启动,但任务写入与身份必须重新切开。

Codex Cloud 会缓存容器状态以加快新任务和后续对话。当前公开文档说明,缓存可保留至多 12 小时;首次缓存时 Clone 默认分支并运行 Setup,恢复时 Checkout 当前任务分支并可执行 Maintenance Script。Setup、Maintenance、环境变量或 Secret 发生变化时,缓存会失效。14

缓存的是“准备好的环境状态”,不是“把上一个任务继续交给下一个用户”。以下是从安全语义推导出的参考设计要求,并非 OpenAI 对内部恢复流程的逐项披露:

重新绑定当前 Repository / Branch
重新建立 Task Identity
确认旧进程已经清空
重新应用 Network Policy
重新注入当前环境变量
不恢复上一个任务的 Secret
创建当前任务自己的工作写层

对于 Business 与 Enterprise 环境,某些缓存可以被同一环境的授权用户共享,这更要求缓存内容不包含个人 Token、任务私有文件或后台进程。14

第六层:云端状态不等于聊天记录。

ChatGPT Work Cloud 的 Conversation、Hosted Execution State、Snapshot、Library File 和 Connected App 内容具有不同存储与生命周期。技术上,这说明一个长任务至少存在两类状态:

Agent Logical State
messages、tool calls、plan、approval、event sequence
Hosted Execution State
sandbox filesystem、runtime state、snapshot、artifact

删除或结束聊天,不应被简单理解为所有执行快照和文件立刻同时消失;相反,执行平台需要通过明确的资源引用和回收策略关联这些对象。19

基于公开产品行为,可以推导出一条 Codex Cloud 编码任务的参考执行链:

用户从 Web、Desktop 或 Mobile 提交 Cloud Task
服务端校验 Workspace 权限并绑定临时任务身份
创建隔离 Container 或恢复安全缓存
Checkout 指定 Repository 与 Commit
Setup Script 联网安装依赖并使用 Setup Secret
结束 Setup Session,移除 Secret
应用 Agent Phase 的 Egress Policy
Codex Harness 循环生成与执行终端命令
Shell 状态和输出进入服务端任务事件通道(架构推断)
Web / Desktop / Mobile 从事件序列订阅进度
输出回答、Diff、Commit、PR 或 Artifact
保留显式结果,回收或缓存执行环境

6.4 同一个任务在 Codex Local 与 Cloud 中如何执行#

假设用户在 ChatGPT 中提出:

修复订单查询接口的超时问题,运行完整测试,并给出一份可以审查的修改。

这条自然语言目标不决定任务在哪里运行。真正决定安全边界的是用户选择 Local、Worktree 还是 Cloud,以及任务获准使用哪些工具。

在 Local 中,任务从用户已有状态出发。

ChatGPT Desktop 打开本地 order-service
Codex 读取当前 Checkout 和未提交修改
Permission Profile 允许写 Workspace,默认禁网
OS Sandbox 启动 ripgrep、Maven、JVM 和测试 Worker
若 Maven 缓存不足,请求访问指定依赖域名
用户批准一次受限网络能力
修改直接落在 Local Workspace 或 Agent Worktree
IDE 立即看到 Diff,用户继续手工调试

本地模式的上下文最丰富,因为它能看到未提交代码、现有构建缓存和本机服务;同时风险也最贴近用户设备。Permission Profile、环境清理、网络代理和 OS Sandbox 必须共同阻止任务顺手读取 SSH Key、访问 VPN 内网或控制 Docker Daemon。

在 Cloud 中,任务从显式提供的上下文重新构造环境。

用户提交 Cloud Task 并选择仓库与分支
服务端创建独立 Container 和任务身份
Checkout 远端仓库,不包含本机未提交修改
Setup 使用 Secret 拉取私有依赖
Secret 被移除,Agent 网络默认关闭
Codex 修改代码、运行测试并生成 Diff
事件持续返回 Web / Desktop / Mobile
用户审查结果并选择创建 PR

云端模式天然无法看见用户电脑上的临时文件、浏览器登录态和本地数据库,除非用户通过上传、连接工具或网络配置显式提供能力。它的环境更可复制,也更容易在用户离开设备后继续运行。

双端切换最容易产生的错误假设,是把 UI 位置当成执行位置。

在桌面应用里查看 Cloud Task
不代表 Cloud Task 已迁移到本机
在移动端继续 Cloud Task
不代表命令在手机上执行
在 Web 页面提交任务
不代表代码运行在浏览器 JavaScript Sandbox

真正应该追踪的是:

execution_location
workspace_source
command_identity
network_policy
credential_source

只要这五个值清楚,用户从哪个屏幕观察任务就只是控制面的变化。

同一个 Coding Agent 任务在 Codex Local 与 Codex Cloud 中的两条执行链

图 12:Local/Worktree 在本地受限边界内修改真实工作区;Cloud 在远端隔离环境中执行并返回 Diff、PR 或 Artifact。浏览器本身不承载 Shell。

6.5 从 Codex 案例提炼的技术原则#

从 ChatGPT Work 与 Codex 的双端实现中,可以提炼出十条更完整的技术原则:

  1. 执行位置是安全模型的一部分。 同一个聊天界面可以承载本地和云端任务,不能根据 UI 判断命令在哪里运行。
  2. 本地执行不等于本地推理。 文件和命令可以留在设备上执行,但模型推理仍可能使用云服务,必要上下文需要跨边界传输。
  3. Sandbox 描述不等于 Sandbox 强制。 Prompt 中的权限说明用于约束模型行为,最终文件和网络边界必须由操作系统执行。
  4. 一个 Agent 拥有多条工具通道。 Shell、Browser、Computer Use、Connector 和 MCP 必须分别建模,关闭 Shell 网络不等于其他通道离线。
  5. Worktree 不是安全沙箱。 它隔离 Git 修改并支持并行工作,但仍需叠加 OS Sandbox 才能限制文件、网络和进程能力。
  6. 本地端以权限做减法。 从真实用户身份、文件、Socket、浏览器和 VPN 能力中,只保留当前任务需要的最小集合。
  7. 云端以权限做加法。 从空白任务身份开始,显式加入仓库、工具、网络、连接应用和短期凭证。
  8. 进程树需要稳定可识别的身份。 Windows 的专用 Sandbox User、Linux 的 cgroup/Namespace,都是为了让限制覆盖 Git、Python、Node 等任意后代进程。
  9. 环境准备与动态 Agent 执行必须分阶段。 Setup 可以拥有安装依赖所需的网络和 Secret,Agent 接管前要单向撤销这些能力。
  10. 客户端连接与任务生命周期必须解耦。 云任务可跨 Web、Desktop 和 Mobile 继续,说明实时连接只是订阅通道,任务事实和执行状态必须保存在服务端。

Codex 案例最重要的价值,不是证明某个产品使用了哪一种沙箱,而是展示了一套统一语义怎样落到两种完全不同的世界:本地端围绕真实设备构造最小权限,云端围绕临时任务构造最小能力。两边的砖块不同,但砌的是同一堵墙。


七、结语:分别理解两种执行面,才能真正理解 Agent Sandbox#

Agent Sandbox 不是一个抽象的“安全容器”,而是模型动作进入不同执行环境后的具体实现。

本地客户端的技术主线是:

从真实用户会话出发
→ 切出可写工作区
→ 构造受限身份
→ 清理环境与本地 Socket
→ 约束网络和进程树
→ 将结果写回本地项目

Web 云端的技术主线是:

从空白任务身份出发
→ 创建临时 RootFS 或 MicroVM
→ Clone 仓库并建立私有写层
→ 分离 Setup 与 Agent Phase
→ 通过代理提供网络和凭证
→ 导出结果,回收环境或按隔离策略缓存 / 快照

具体原语因平台而异:Linux 主要使用 Namespace、cgroups、Capabilities、Seccomp 与 LSM;macOS 使用 Seatbelt;Windows 组合 Token、SID、ACL、Firewall 与 Job Object;云端还可叠加容器、用户态内核或虚拟化边界。这些机制在两端承担的具体工作并不相同:本地沙箱要驯服一台已经拥有大量权限的真实电脑,云端沙箱要从零组装一台只拥有任务所需能力的临时电脑。

真正成熟的沙箱不会赌模型永远正确。它把每一条动态命令都当成可能出错的代码,让进程从出生起就在边界内,让网络和凭证只能沿受控通道流动,并确保任务结束时整套执行环境可以被彻底回收。

当 Agent 开始拥有手脚,本地 Command Runner 和云端 Sandbox Runtime 就成为它的两套骨骼。只有分别拆开这两套实现,才能看清“副作用控制”究竟如何从 Tool Call 一路落到操作系统内核。


参考资料#

Footnotes#

  1. OpenAI, Sandbox: How sandboxing works across ChatGPT and Codex clients 2

  2. OpenAI, Codex environments 2 3

  3. Linux man-pages, namespaces(7)

  4. Linux man-pages, user_namespaces(7)

  5. Linux Kernel Documentation, Control Group v2

  6. Linux man-pages, seccomp(2)

  7. Linux man-pages, seccomp_unotify(2)

  8. Linux Kernel Documentation, Landlock: unprivileged access control

  9. Linux Kernel Documentation, Overlay Filesystem

  10. Linux man-pages, openat2(2)

  11. Microsoft Learn, Job Objects

  12. gVisor, Introduction to gVisor security

  13. Firecracker MicroVM

  14. OpenAI, Cloud environments 2 3 4 5 6 7 8

  15. OpenAI, Agent approvals & security 2

  16. OpenAI, Agent internet access 2

  17. OpenAI, ChatGPT Work Overview 2

  18. OpenAI, ChatGPT Work local security 2 3 4

  19. OpenAI, ChatGPT Work cloud security 2 3 4 5 6

  20. OpenAI, Unrolling the Codex agent loop 2

  21. OpenAI, Codex Git worktrees

  22. OpenAI, Codex permission profiles 2 3

  23. OpenAI, Windows sandbox 2 3

  24. OpenAI, Building a safe, effective sandbox to enable Codex on Windows 2 3 4 5 6

  25. OpenAI, Use ChatGPT: choose cloud or local work

Agent Sandbox 技术原理:本地客户端与 Web 云端执行环境的双重实现
https://jupiter-ws.cn/posts/ai-coding/agent-sandbox-local-cloud-architecture/
作者
Jupiter
发布于
2026-09-04
许可协议
CC BY-NC-SA 4.0