背景
在前面一篇文章中,我讨论了多种不同的 AI Agent 沙箱环境:加固容器、microVM、gVisor、策略层,以及云托管沙箱服务。那篇文章更偏向一个问题:当我们要执行不可信的 Agent 生成代码,或者要给第三方用户提供一个远程执行环境时,应该把隔离边界放在哪里。
但是看了 Claude Code 和 Codex 的实现之后,会发现本地 Agent 的问题不太一样。我们日常使用的 Claude Code 或 Codex,通常不是一个面对陌生用户开放的多租户代码执行平台,而是一个在自己电脑上、自己仓库里、有人监督的开发助手。它需要读代码、改代码、跑测试、访问模型服务,有时还要访问 GitHub、npm、PyPI、Docker、云服务 CLI,甚至本地代理。
所以,本地 Agent 的沙箱目标不是简单地回答“要不要给它一个容器”。它真正要解决的是:如何让 Agent 在足够有用的同时,不至于拿到整台机器、所有凭证和任意网络出口。
本地沙盒环境的新挑战
想象一下,当你在本地运行 Agent 的时候,比如你希望 Agent 帮你 review 一个 PR,那么 Agent 至少需要这些能力:
- 能够访问远程大模型服务;如果你使用本地模型,这一项可以减少;
- 能够访问 PR 或代码仓库;这可能是网络权限,也可能是本地文件读取权限;
- 能够克隆、构建、测试项目;这通常意味着对工作目录的读写权限;
- 能够临时访问某些开发工具的认证信息;比如 GitHub、npm、私有包仓库或云厂商 CLI。
把这些需求抽象一下,看起来只有两类权限:访问网络,以及读写本地文件系统。但真正难的是粒度:
- 不是“能不能访问网络”,而是“能访问哪些域名、哪些本地地址、哪些 Unix socket”;
- 不是“能不能读写文件”,而是“能写哪个工作区、能不能读主目录、能不能碰 SSH key、云凭证和 shell 配置”;
- 不是“命令能不能运行”,而是“这个命令是在沙箱内运行,还是需要用户批准后越界运行”;
- 不是“是否隔离 Agent”,而是“只隔离 shell 子进程,还是把 MCP、hooks、浏览器、桌面能力也一起纳入边界”。
这也是为什么本地 Agent 往往不会直接选择一个重型容器或 microVM 作为默认体验。它需要在真实工作目录里工作,需要和已有工具链互操作,还要根据任务逐步披露权限。于是主流方案变成了“操作系统沙箱 + 权限审批 + 网络代理 + 少量可配置策略”的组合。
不同 Agent 的实现方式
对于这种本地 Agent 遇到的挑战,不同的流行 Agent 的实现方式其实都大差不差,这里我就以比较流行的两种 Agent:Claude Code 和 Codex 为例介绍一下他们的实现,为了更加清晰地表达,我这里先把本地 Agent 的沙箱拆成几层:
- 执行层:Agent 生成的 shell 命令、测试命令、包管理器命令以及它们的子进程在哪里运行;
- 文件系统层:命令可以读哪里、写哪里,凭证文件和配置文件是否被保护;
- 网络层:命令能不能出网,能去哪些域名,能不能访问本机回环地址、内网地址、Unix socket 或上游代理;
- 控制层:当 Agent 想越过边界时,是直接失败、自动拒绝,还是弹出审批;
- 云端形态:如果不是本地运行,而是 Codex cloud 或 Claude Code on the web,隔离边界通常会变成托管容器或托管 VM。
这几层经常被混在一起讨论,但它们不是同一件事,因为它们的具体实现时会分别对应到: seccomp、Seatbelt、bubblewrap、容器、代理、审批按钮,所以这里划分到不同层上。
Codex 的实现方式
根据 OpenAI 的官方文档,Codex 的本地形态,包括 ChatGPT desktop app、Codex CLI 和 IDE extension,默认不是把每次任务都放进一个完整 Docker 容器或 microVM,而是使用操作系统强制执行的沙箱,再叠加审批策略。官方文档的表述是:本地 Codex 默认关闭网络访问,使用 OS-enforced sandbox 限制它能触达的范围,通常限制在当前 workspace,并通过 approval policy 决定什么时候必须停下来问用户。
从使用者角度看,Codex 主要有几个常见模式:
read-only:Agent 可以查看文件,但不能自动修改文件或运行需要权限的命令;workspace-write:Agent 可以在当前工作区内读写文件,并在边界内运行常规命令,这是本地开发里比较低摩擦的默认形态;danger-full-access:去掉文件系统和网络边界,适合你明确希望 Agent 拿到完整权限的场景,但不应该作为默认安全模型。
一个容易忽略的点是,Codex 的沙箱作用在 spawned commands 上。也就是说,当 Agent 运行 git、测试命令、包管理器、构建脚本时,这些命令和它们的子进程会继承同样的边界。这一点比“只限制内置文件编辑工具”更关键,因为真正危险的动作往往发生在 shell 子进程里。
在不同操作系统上,Codex 使用平台原生机制来实现这个边界。macOS 上使用系统内置的 Seatbelt;Linux 和 WSL2 上当前官方文档要求安装 bubblewrap,Codex 会使用 PATH 中第一个 bwrap,没有时才回退到 bundled helper;Windows PowerShell 下使用 native Windows sandbox,WSL2 下则走 Linux 沙箱实现。
这意味着 Codex 的本地沙箱更接近“进程级 OS policy sandbox”,而不是传统意义上的 OCI 容器。Linux 下的 bubblewrap 会用 Linux namespace 等内核能力构造隔离环境,但它不是让你进入一个完整业务容器镜像,也不是每个任务启动一台独立内核的 microVM。
网络控制
Codex 把“是否允许命令联网”和“联网后可以访问哪里”分成了两件事。
默认的 workspace-write 模式下,命令网络访问是关闭的。如果你只打开 sandbox_workspace_write.network_access = true,那表示允许命令直接出网;如果还想限制目的地,就需要打开 network_proxy。官方文档给出的模型是:命令网络访问已经打开之后,network_proxy 才会把 scripts、programs 和 subprocesses 的网络流量约束到配置的 domain policy 里。
这个设计有三个很实际的后果:
- network off 加 network proxy on:网络仍然关闭,proxy 不会凭空授予出网能力;
- network on 加 network proxy off:命令获得不受域名策略约束的直接出网能力;
- network on 加 network proxy on:命令可以出网,但目的地受域名策略约束。
Codex 的域名策略是 allowlist-first:没有允许的域名默认不能访问,deny 优先于 allow。它还默认阻止 loopback、link-local 和 private network destination,除非你显式允许某个本地 IP、localhost,或者开启更宽的本地访问。官方文档还提到会做 best-effort 的 DNS 和 IP 分类检查,以降低 DNS rebinding 风险,但如果 hostile DNS 已经进入威胁模型,仍然建议在更低层做 egress control。
如果你的本机网络必须通过代理才能访问外部网络,这一层尤其重要。更合理的模型应该是:沙箱内命令先被 Codex 的 network proxy 约束,再由它连接到公司代理或本地上游代理。否则,如果你把 HTTP_PROXY、HTTPS_PROXY 或 ALL_PROXY 直接暴露给沙箱内命令,而这个代理又能访问任意域名,那么它可能绕过 Codex 本来想表达的域名策略。Codex 文档里也能看到和 upstream proxy、本地绑定、Unix socket allowlist 相关的配置,这说明代理链路本身已经是网络边界的一部分。
我当前本机的 codex-cli 0.144.0-alpha.4 二进制里,也能观察到 network-proxy、network_policy、allowed_domains、linux-sandbox、seatbelt 等字符串。这只能作为实现线索,不能替代公开文档;但它和官方文档里的 network proxy、平台原生沙箱描述是吻合的。
Codex cloud
Codex cloud 和本地 Codex 不是同一种隔离边界。官方文档描述的 Codex cloud 是 OpenAI 托管的 isolated containers,并且使用两阶段运行模型:setup 阶段可以联网安装依赖,agent 阶段默认离线,除非你为对应环境开启 internet access。配置给 cloud environment 的 secrets 只在 setup 阶段可用,进入 agent 阶段前会被移除。
所以,如果按隔离路线分类,本地 Codex 更像 OS policy sandbox 加审批和网络代理;Codex cloud 更像托管容器执行环境。它们解决的不是同一个问题:前者服务于本机开发体验,后者服务于远程托管任务,这也和我开头所描述的两种不同场景一致。
Claude Code 的实现
Claude Code 的默认安全模型首先是 permission-based architecture,而不是完整虚拟化。官方文档说,Claude Code 默认是严格只读权限;当需要编辑文件、运行测试、执行命令时,会请求用户明确批准。它有一组内置的只读命令,比如 ls、cat、git status,这类命令可以不弹窗直接运行;而会修改系统的 Bash 命令需要批准。
这套默认模型的边界更多在控制层:Claude Code 先判断某个工具调用或命令是否需要批准,再让用户选择批准一次、批准一类操作,或者拒绝。比如 curl、wget 这类会从 Web 获取内容的命令默认不会自动批准;发起网络请求的工具也默认需要用户批准。它还强调 working directory boundary:默认只能写启动目录及其子目录,访问父目录或其他路径需要额外批准。
这解释了一个常见误解:默认的 Claude Code 并不等于“所有东西都已经被放进容器或 VM”。它更像一个权限系统,把危险动作挡在执行前。
/sandbox 命令
Claude Code 也有内置的沙箱能力,但它的范围要看清楚:/sandbox 配置的是 sandboxed Bash tool。也就是说,它主要隔离 Bash 命令以及这些命令产生的子进程,而不是天然把整个 Claude Code 进程、所有 MCP server、hooks、内置 Read/Edit/WebFetch 工具都包进去。
官方文档里,/sandbox 的实现是:macOS 使用 Seatbelt;Linux 和 WSL2 使用 bubblewrap;Linux/WSL2 还依赖 socat 把网络流量转发到沙箱外的代理。native Windows 不支持这个内置 Bash sandbox,Windows 用户需要在 WSL2 中运行,或者选择容器、VM 等其他路线。Linux 上还有一个可选的 seccomp filter,由 @anthropic-ai/sandbox-runtime 提供,用来增加 Unix domain socket blocking。
Claude Code 的 /sandbox 默认文件系统策略也很有代表性:
- 默认可写:当前工作目录及其子目录,加上 session temp directory;
- 默认可读:整台电脑大部分路径仍然可读,除了被显式 deny 的目录;
- 默认阻止:不能修改工作目录和 temp 之外的路径,例如 shell 配置、系统二进制等;
- 可配置:可以通过
sandbox.filesystem.allowWrite、denyWrite、denyRead、allowRead缩小或放宽边界。
这里有个安全细节很重要:Claude Code 文档明确提醒,默认读策略仍可能读到 ~/.aws/credentials、~/.ssh 这类凭证文件。要保护它们,需要显式配置 sandbox.credentials 或 denyRead。也就是说,沙箱不是“默认保护所有秘密”,它只是提供可执行的策略层。
网络控制
Claude Code 的 /sandbox 网络隔离靠运行在沙箱外的 proxy server。默认没有预先允许任何域名;当某个命令第一次需要访问新域名时,Claude Code 会弹出批准提示。批准后,当前 session 后续访问同一 host 不再重复提示。你也可以通过 allowedDomains 预先允许域名,或者在 managed settings 中开启 allowManagedDomainsOnly,让未允许的域名直接失败而不是弹窗。
内置代理默认基于请求 hostname 做 allowlist enforcement,并且默认不终止或检查 TLS。较新的 network.tlsTerminate 设置可以让内置代理自己终止 TLS,主要用于 credential masking:沙箱内命令拿到的是一个 sentinel token,只有请求离开沙箱并访问允许的 host 时,代理才把 sentinel 替换成真实凭证。这个能力很有用,但也意味着代理必须能看到请求内容,所以它应该只在你或管理员控制的设置里启用。
这和 Codex 的 network proxy 思路很接近:真正的域名级网络策略不适合只靠 seccomp 这种 syscall filter 来做,因为 seccomp 看不到 HTTP hostname,也不理解 TLS、代理链路或业务域名规则。实际产品一般会把网络流量收束到一个代理,再由代理执行域名、凭证、审计和上游代理策略。
沙箱的边界限制
Claude Code 文档把边界说得很直白:内置 Bash sandbox 只覆盖 Bash 命令和它们的子进程。其他内置工具,比如 Read、Edit、WebFetch,不是任意代码执行,它们由 Claude Code 进程里的 permission rules 控制。MCP servers 和 hooks 则是独立进程,默认在 host 上运行,并不因为 Bash sandbox 开启就自动获得同样隔离。
如果你想把 Claude Code 整个会话都放进一个 OS 边界里,Anthropic 给了几条路线:
- 使用
@anthropic-ai/sandbox-runtime包住整个 Claude Code 进程;这个 runtime 使用和内置 Bash sandbox 相同的 Seatbelt 或 bubblewrap 机制,但当前仍是 beta research preview; - 使用 dev container,让 Claude Code、终端和构建工具都跑在 Docker 容器内,宿主机只挂载工作区;
- 使用自定义 Docker/OCI 容器,叠加自己的网络策略、挂载策略和 seccomp profile;
- 使用专用 VM 或 microVM,在需要独立内核边界时采用更强隔离;
- 使用 Claude Code on the web,让任务跑在 Anthropic 托管的 isolated VM 中,由网络代理和 scoped GitHub credential 控制外部访问。
所以 Claude Code 的结论是:默认是权限系统,/sandbox 是 Bash 子进程沙箱,sandbox-runtime/container/VM 才是更完整的执行环境隔离。
两者共同的设计取舍
Codex 和 Claude Code 的本地方案都不是传统“纯容器派”,也不是“每个任务一个 microVM”。它们面对的是本地开发助手这个场景:用户希望 Agent 能直接操作真实仓库、真实工具链和真实编辑流程,同时又不希望它默认拥有整台机器。
因此,它们都走向了类似的组合:
- 用 OS 级机制约束 shell 命令和子进程;macOS 主要是 Seatbelt,Linux/WSL2 常见是 bubblewrap;
- 用权限/审批系统处理越界动作;该问人的时候问人,而不是一开始就给无限权限;
- 用网络代理表达域名级策略;因为 syscall 层不知道业务域名,容器网络也不天然知道哪些 host 是安全的;
- 用配置文件或 managed settings 把个人偏好和组织策略固化下来;
- 对云端形态使用托管容器或托管 VM,因为远程任务的威胁模型和本地交互式开发不同。
这也解释了为什么“为什么不用 cgroup 和 namespace 做完整容器”这个问题不能只从安全强度回答。容器当然可以提供隔离,但本地 Agent 的默认路径还要考虑开发体验、现有工作区、逐步授权、跨平台一致性、本地代理、IDE 集成和用户审批。对本地开发助手来说,一个轻量的 OS policy sandbox 加精细审批,往往比每次都启动完整容器更贴近产品目标。
本地代理避坑
从前面的 Claude Code 和 Codex 的网络控制中,我们已经了解到了他们都通过网络代理来控制网络的访问边界。但是,很多开发者的机器上都可能会有 Clash、Surge、公司代理、Docker daemon、数据库端口、Kubernetes API、云 metadata proxy 或各种本地管理接口,这些工具对我们来说,可能只是“本机环境”,但是对 Agent 生成的命令来说,它们就可能是绕过网络策略、读取凭证或横向移动的入口。
所以,如果你希望保持网络策略有效,最好遵循几个原则:
- 不要把万能上游代理直接暴露给沙箱内命令;优先让沙箱网络代理先执行域名策略,再连接上游代理;
- 对
localhost、127.0.0.1、内网网段和 Unix socket 默认保持拒绝,只为必要目标开窄例外; - 对 Docker socket、SSH agent socket、云凭证文件、本地数据库端口保持额外警惕;
- 如果任务会处理不可信网页、issue、PR 评论或文档内容,把网络出口过滤当成必选项,而不是可选增强。
这也是浏览器/桌面类 Agent 更危险的原因:风险不只是 Agent 自己生成了危险代码,也可能是它在网页或应用里读到了恶意 prompt injection,然后“自愿”去执行危险动作。命令行沙箱、浏览器沙箱、桌面沙箱最后都要回到同一个问题:谁能触达文件、凭证和网络出口。
结论
最后我用一句话总结一下这篇文章想讨论的话题:本地 Agent 的沙盒环境不是“把 Agent 放进一个更安全的容器”这么简单,而是把本地开发场景拆成了几个可控边界:shell 子进程用 OS sandbox,文件访问用 workspace/path policy,网络访问用 proxy 和 domain policy,越界动作交给 approval policy。
对个人开发者来说,这个模型的好处是低摩擦:Agent 可以在项目里持续推进,只有越界时才停下来。对于平台来说,它提醒我们不要把“本地 Agent 沙箱”和“执行不可信用户代码的多租户沙箱”混为一谈。前者重在精细权限和人类监督,后者仍然应该优先考虑容器加固、gVisor、microVM、VM、托管沙箱和强制 egress control。