第 13 章:Sandboxes、网络策略与平台边界
阅读契约: 用本章跟随一个已获批准的 tool action 进入 containment。请把三个 owner 分开看:permission profile 负责描述允许的副作用;sandbox manager 负责把 profile 变成 execution request;platform backend 负责按操作系统能表达的方式执行。读完之后,你应该能解释为什么 approval 不是 sandbox bypass,为什么“network enabled”不等于“没有网络边界”,以及为什么 macOS、Linux、Windows 不是可以互换的 sandbox 方言。

源码边界: 本章解释的是 OpenAI Codex 在 commit 569ff6a1 的公开源码快照。凡是链接到该 commit 的 file path、function name、enum case 和短源码片段,都是 verified source。policy compiler、platform dialect、containment ledger 这类说法是基于可见源码的 surrounding contract inference,不是对 OpenAI 私有服务内部的断言。跨平台对比只描述 enforcement shape 的差异;本章不声称 macOS Seatbelt、Linux bwrap/seccomp、Windows identity/ACL/WFP 提供完全相同的安全保证。
第 12 章停在最后一个 approval 边界:tool call 已经被路由,hooks 可以观察或阻断,permission-request hooks 可以回答审批,Guardian 或用户可以批准,sandbox denial 还可能触发一次新的 retry decision。但真正触碰主机的部分还没开始。Approval 回答的是“Codex 可不可以尝试这个副作用?”Sandboxing 回答的是“这个已获批准的 attempt 仍然能触碰什么?”
源码把这两个问题分得很开。ReviewDecision::Approved 不会让进程突然拥有无限权限。Permission profile 本身也不会执行命令。Platform sandbox 不是通过读 shell 字符串再猜它要做什么来选择的。运行时把一次 execution attempt 放进一条链:
- 解析 permission profile。
- 合并这条 command 获得的 additional permissions。
- 把 effective profile 拆成 filesystem policy 和 network policy。
- 根据 tool preference、policy shape、managed-network requirements 和平台支持情况选择初始 sandbox type。
- 把 command 或 execution request 改写成平台 backend 能执行的形状。
- 由 executor 运行改写后的 request,并把 evidence 交回 runtime。
这就是本章的核心判断:Codex containment 是一条 policy transform pipeline,不是一个单一 sandbox 开关。
一、Approval 结束之后,Containment 才开始
最容易犯的错,是把 approval 当成“关闭 sandbox”的动作。正常路径不是这样。Approval 只允许 tool attempt 继续。后续的 SandboxAttempt 仍然把 manager、permission profile、selected sandbox type、managed network flag、sandbox cwd、Linux helper path 和 Windows sandbox settings 带进 transform。
这条桥在 SandboxAttempt::env_for 里很清楚:
self.manager
.transform(SandboxTransformRequest {
command,
permissions: self.permissions,
sandbox: self.sandbox,
enforce_managed_network: self.enforce_managed_network,
network,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.map(PathBuf::as_path),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
这个片段删掉了后续包装,只保留 handoff。重点不是 Rust 结构体语法,而是 owner boundary:tool attempt 携带的不是裸 argv,而是必须被降低成 execution request 的 policy context。

这样 retry path 才有清晰语义。第一次 sandboxed attempt 被平台拒绝时,orchestrator 可以为 no-sandbox retry 问一个新的 approval question。这个 retry 是新的风险决策,不是藏在 approval 里的静默逃逸。把这两个阶段混起来,后面的安全分支都会读错。
二、Permission Profile 是 Codex 编译的单位
旧的直觉是“sandbox mode”。这个快照里的源码更接近“permission profile”。Profile 可以来自 built-ins、自定义配置,也可以叠加 command-scoped additions。它随后被编译成两个 runtime policies:FileSystemSandboxPolicy 和 NetworkSandboxPolicy。
Built-in profile resolver 在 core/src/config/permissions.rs 里把 profile name 映射成 read_only、workspace_write 或 Disabled。自定义 profile compiler 会遍历 filesystem entries,对非 macOS 平台不支持的 glob 形状发 warning,校验 glob scan depth,并计算 network policy(compile_permission_profile)。Network runtime policy 的枚举很小:
pub enum NetworkSandboxPolicy {
Restricted,
Enabled,
}
小不代表简单。Restricted 后续可能意味着“禁网”、“只允许 proxy sockets”、“unshare network namespace”,也可能意味着“准备 Windows firewall/WFP 状态”,取决于平台和 managed-network settings。Enabled 在 managed proxy 存在时也可能经过 proxy environment injection。这个 enum 是 policy signal,不是完整 packet-filter 说明书。
2.1 Additional Permissions 保持局部
Command-scoped additional permissions 会在 enforcement 前合并。在 policy_transforms.rs 中,additional filesystem paths 会被 normalize,重复条目会被去掉,glob 的 read/write addition 会被拒绝,因为这条 additional-permission 路径只支持 deny-read glob entries。随后 runtime 重新计算 effective profile:
let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
let effective_file_system_policy =
effective_file_system_sandbox_policy(&file_system_policy, additional_permissions);
let effective_network_policy =
effective_network_sandbox_policy(network_policy, additional_permissions);
PermissionProfile::from_runtime_permissions_with_enforcement(
permission_profile.enforcement(),
&effective_file_system_policy,
effective_network_policy,
)
这个源码形状保护了一个重要 invariant:给某个 tool 额外放开一个 writable root 或 network allowance,不会把 session base policy 原地改写。Transform 接收的是本条 command path 的 effective profile,而外层 runtime 仍然能分辨 grant 从哪里来。
2.2 Profile 先拆 policy,再进平台代码
拆成 filesystem policy 和 network policy 发生在 macOS、Linux、Windows 代码表达它们之前。这个顺序能防止两个错误说法:不能说“Linux 就是 sandbox”,也不能说“Seatbelt 就是 policy”。Platform helpers 是 enforcement dialects;permission profile 才是 runtime 想要降低的 source-level contract。
| Profile 压力 | 源码机制 | 保护的 invariant |
|---|---|---|
| 默认执行需要一个合理 containment baseline | permissions.rs 中的 built-in profile name 和 resolver | Session 从命名 profile 开始,而不是靠命令字符串临时猜权限。 |
| 自定义 filesystem shape 可能包含不被平台支持的 glob | compile_permission_profile 里的 compiler warnings | 平台差异会暴露出来,而不是被静默放宽。 |
| 单条 command 可能获得更窄的 grant | policy_transforms.rs 中的 additional-permission normalization 和 merge | 局部 grant 不会变成全局 session authority。 |
| Managed network 可能在 filesystem 很宽时仍要求平台 forcing | should_require_platform_sandbox | Network requirements 可以独立于 file writes 强制选择 sandbox。 |
三、Sandbox Manager 选择平台方言
选出来的 sandbox type 在 sandboxing/src/manager.rs 里是显式枚举:
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
}
这些名字需要小心读。LinuxSeccomp 是 manager 层的 sandbox type 名字,但真实 Linux 路径可以包含 bwrap filesystem layout、namespace work,然后再应用 seccomp。WindowsRestrictedToken 命名了一个 Windows sandbox branch,但 elevated Windows backend 还会准备 identities、ACL、firewall rules 和 WFP state。这个 enum 是 runtime routing value,不是完整 security design document。
平台选择本身很窄:
pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType> {
if cfg!(target_os = "macos") {
Some(SandboxType::MacosSeatbelt)
} else if cfg!(target_os = "linux") {
Some(SandboxType::LinuxSeccomp)
} else if cfg!(target_os = "windows") {
if windows_sandbox_enabled {
Some(SandboxType::WindowsRestrictedToken)
} else {
None
}
} else {
None
}
}
随后 SandboxManager::select_initial 应用 tool preference:
| Preference | Runtime 语义 |
|---|---|
Forbid | 选择 SandboxType::None。这条 tool path 明确不走 platform sandbox。 |
Require | 当前 OS 暴露平台 sandbox 时,要求使用它。 |
Auto | 只有 filesystem、network 或 managed-network requirements 需要时才要求 platform sandbox。 |
Transform step 会拿着 selected type 构造平台形状的 SandboxExecRequest(manager.rs)。有三个细节最关键:
effective_permission_profile是在 transform 内部、取出 command-scoped additions 之后计算的。- macOS 和 Linux 会把
argv改写成 wrapper/helper command。 - Windows 在这个 transform 层保持 command vector 不变,把 sandbox metadata 带给 Windows execution backend。
因此,“命令字符串看起来不危险”不是边界。Runtime 不是事后从 shell syntax 猜 authority,而是先编译 policy,选择 dialect,再让 dialect 表达边界。
四、平台后端并不等价

4.1 macOS 生成 Seatbelt Profile
在 macOS 上,transform 会为固定 Seatbelt executable 构造参数。MACOS_PATH_TO_SEATBELT_EXECUTABLE 是 /usr/bin/sandbox-exec,不是通过用户 shell 的 PATH 找到的便利命令。create_seatbelt_command_args 会把 read policy、write policy、deny-read glob policy、network policy 和 directory parameters 拼成一个 profile。
macOS network generator 展示了最需要谨慎的部分。在 dynamic_network_policy_for_network 里,proxy ports、proxy configuration、managed-network enforcement 和 Unix-domain socket needs 都可能强制进入 restricted-network branch。在这个 pinned snapshot 中,proxy configuration 和 enforce_managed_network 已经是该 branch condition 的一部分,所以它们会停留在 restricted path。这个 branch 只会为显式 proxy ports 添加 outbound loopback allowances,并按条件添加 local-binding 或 Unix-socket allowances;如果没有推断出 loopback proxy ports,生成的 profile 仍然是受限的,而不是静默打开 broad outbound traffic。
所以 macOS 边界不只是“跑 sandbox-exec”。准确说是:生成一个能表达该 profile 的 policy,覆盖 filesystem roots、protected metadata、denied read patterns、sockets 和 network/proxy behavior,然后通过固定平台 runner 执行。
4.2 Linux 先构造 Filesystem View,再应用 Seccomp
Linux 上,manager 会走 Codex Linux sandbox helper。Helper 在 linux_run_main.rs 的注释直接写出执行顺序:
/// 1. When needed, wrap the command with bubblewrap to construct the
/// filesystem view.
/// 2. Apply in-process restrictions (no_new_privs + seccomp).
/// 3. `execvp` into the final command.
这就是为什么本文说 “Linux bwrap plus seccomp”。bwrap 构造 filesystem view 和 namespace 条件;inner stage 在最终 execvp 之前应用 seccomp/no-new-privileges。代码里也有 legacy Landlock branch,用于兼容的 legacy policies;但当前 bwrap 路径不会在所需平台行为不可用时假装成功。Manager 在构造 Linux command 前会检查 WSL1 和 bubblewrap support(manager.rs)。
Network 会进入 bwrap mode。bwrap_network_mode 在 managed proxy routing 开启时选择 ProxyOnly,在 network policy enabled 时选择 FullAccess,否则选择 Isolated。Managed proxy routing 随后在 host 侧准备 proxy sockets,并在 namespace 内重写 proxy environment(proxy_routing.rs)。
4.3 Windows 把工作放进 Setup 和 Runner Backends
Windows transform branch 很容易被误读,因为 manager 没有像 macOS/Linux 那样 prepend wrapper executable。Transform 中的 WindowsRestrictedToken 返回原始 argv,同时把 sandbox type、Windows level、private desktop flag、permission profile、filesystem policy 和 network policy 放进 SandboxExecRequest(manager.rs)。
真正的 enforcement work 在 Windows sandbox crate 里。Setup refresh 会构造 elevation payload,里面包含 read roots、write roots、deny-write paths、proxy ports 和 local-binding behavior(setup.rs)。Elevated setup path 会构造类似 payload,并判断是否需要 elevation(setup.rs)。Elevated spawn backend 会准备 context,并带着 capability SIDs 和 sandbox credentials 发送 runner request(elevated.rs)。
Identity、ACL、firewall 和 WFP 这些标签不是装饰。spawn_prep.rs 会准备 token、sandbox credentials、capability SIDs 和按 policy 计算的 ACL inputs;acl.rs 会读取并更新 DACL/ACE state。Setup helper 的 firewall.rs 会配置 offline outbound 和 loopback proxy block rules;wfp_setup.rs 会为 offline account 调用 install_wfp_filters_for_account。具体 WFP filter shape 可以在 filter_specs.rs 里直接看到:按 user scoped 的 ICMP、DNS ports 和 SMB ports block。
Legacy backend 有明确限制:它会拒绝 restricted read-only access,并说明该形状需要 elevated Windows sandbox backend(legacy.rs)。这就是“有 Windows 分支”和“每个 Windows backend 都能表达每种 profile”之间的实际差别。
4.4 正确的对照表
| Platform path | Transform 或 backend 改变了什么 | Network expression | 必须诚实说明的边界 |
|---|---|---|---|
| macOS Seatbelt | 用 /usr/bin/sandbox-exec 和生成的 profile arguments 包装 command。 | 生成 policy 允许或拒绝 network、proxy ports 和 selected sockets。 | Policy generation 表达力强,但仍受 Seatbelt grammar 和 endpoint discovery 限制。 |
| Linux bwrap/seccomp | 运行 Codex helper,构造 filesystem/namespace view,再在 execvp 前应用 no-new-privileges 和 seccomp。 | bwrap mode 选择 isolated、full access 或 proxy-only;proxy routes 可以 bridge 进 namespace。 | Host support 很关键:WSL1 和不合适的 bwrap 行为是真实平台边界。 |
| Windows identity/ACL/WFP | Transform 把 metadata 带进 Windows setup 和 runner backends。 | Offline proxy settings、firewall allow/block rules 和 WFP setup 都参与 containment。 | Backend level 很关键;legacy 和 elevated paths 不能执行同样的 policy shapes。 |
SandboxType::None | 不应用 platform sandbox wrapper。 | Host process/network environment 不能被描述成等价 containment。 | 它是显式 unsandboxed path 或 unsupported platform result,不是隐藏 sandbox。 |
五、Managed Network 强于环境变量,弱于通用防火墙

Proxy code 是很多描述走偏的地方。说得太弱,就会变成“Codex 只是设置 HTTP_PROXY”。说得太强,又会变成“Codex 有完整网络防火墙”。源码落在这两者之间。
Proxy builder 可以 reserve HTTP 和 SOCKS listeners,应用 proxy environment overrides,为 loopback 和 private IP literals 设置 NO_PROXY defaults,对齐 websocket proxy environment variables,并在 SOCKS enabled 时设置 ALL_PROXY(proxy.rs)。Domain rules 会被 normalize 成 exact hosts 或 scoped wildcard host patterns,例如 *.example.com 和 **.example.com;denylist 会拒绝全局 * wildcard(policy.rs)。关键区别是 scope:允许的是有边界的 host pattern,而不是 bare match-everything rule。
Limited method rule 在测试里很直接:
assert!(NetworkMode::Limited.allows_method("GET"));
assert!(NetworkMode::Limited.allows_method("HEAD"));
assert!(NetworkMode::Limited.allows_method("OPTIONS"));
assert!(!NetworkMode::Limited.allows_method("POST"));
assert!(!NetworkMode::Limited.allows_method("CONNECT"));
这段测试来自 network-proxy/src/policy.rs。这也是网络图把 GET、HEAD、OPTIONS 画成 limited mode allowed,而把 POST/CONNECT 画成 blocked 的原因。
平台集成让它不只是普通环境变量提示:
- macOS 上,生成的 Seatbelt policy 可以在需要 managed network 时把 network access 限制到 loopback proxy ports 和 selected Unix sockets。
- Linux 上,proxy-only bwrap mode 可以把 proxy routes bridge 进 network namespace,再应用 seccomp。
- Windows 上,setup payload 会携带 proxy ports 和 local-binding behavior,firewall 与 WFP setup 也参与 offline identity boundary。
诚实的边界应该这样说:managed network 在 clients 和 platform constraints 配合时,可以路由并审查 application traffic;但它本身不能证明每一种 packet、DNS behavior 或 host-level escape 都被 proxy layer 控住。
六、失败边界本身就是设计的一部分
源码里有一些很容易被文章磨平的 failure edges。磨平它们会让文章更顺,但更没用。
| 误读 | 源码正确读法 | 为什么重要 |
|---|---|---|
| Approval 意味着“在 sandbox 外运行”。 | Approval 允许 attempt 继续;SandboxAttempt::env_for 仍然带着 policy 和 sandbox metadata 调 transform。 | Reviewer 可以批准命令,但不等于授予无限主机触达。 |
| Permission profile 就是 sandbox。 | Profile 编译成 filesystem/network policies,再由平台 dialect 执行它能表达的部分。 | Config review 和 platform review 是不同工作。 |
| Linux sandboxing 只有 seccomp。 | 当前 helper 可以先构造 bwrap filesystem/namespace view,再应用 seccomp;legacy Landlock 是另一条路径。 | File visibility 和 syscall filtering 是两层。 |
| Windows transform 不包装 argv,所以没有 Windows sandbox。 | Windows 把 sandbox metadata 带进 setup 和 runner backends,而不是复制 Unix wrapper 形状。 | 看起来相同的 command vector,仍可能在不同 identity、ACL/firewall state 下运行。 |
| Managed network 是 packet firewall。 | 它是 proxy boundary,可用时配合 platform forcing,并在可见层做 domain/method policy 和 audit。 | 过度承诺会让 non-proxy-aware programs 和 host networking 的空隙更难讨论。 |
SandboxType::None 是无害 fallback。 | 它是显式 no-platform-sandbox path 或 unsupported-platform result,不能被描述为等价 containment。 | UI、logs 和文章都不应该隐藏 containment 缺席。 |
应用到实践
可以带走的规则很简单:把 authority、profile、transform、backend 放在不同盒子里。
- 区分 approval 和 containment。 Approval 授权 attempt;sandbox transform 仍然决定 process boundary。
- 执行前先编译 profile。 先看 effective filesystem/network policies,再判断平台行为。
- 说清平台方言。 用 Seatbelt、bwrap/seccomp、Windows identity/ACL/WFP 或 no sandbox,而不是笼统说“sandbox”。
- 精确描述网络保证。 Managed proxy 加 platform forcing 很有意义,但它不是通用 packet firewall。
- 把 containment 缺席当成一等状态。
SandboxType::None、missing helpers 和 unsupported host features 都应该在 UI、logs 和文章里可见。
| 当你看到… | 该问什么 | 应该查的源码 owner |
|---|---|---|
| 用户或 hook approval | 它只是 authorize attempt,还是也请求了 no-sandbox retry? | 第 12 章里的 tool orchestrator 和 approval decision flow。 |
| Permission profile name | 它会编译成哪些 filesystem/network policies? | core/src/config/permissions.rs 与 protocol/src/permissions.rs。 |
| 带额外 grant 的 command | 这些 grants 是否只为本条 effective profile normalize 和 merge? | policy_transforms.rs。 |
| Selected sandbox type | 请求的是哪种平台 dialect,当前 host 能不能提供? | SandboxManager::select_initial。 |
| Network claim | 它是 proxy policy、platform forcing、两者都有,还是都没有? | network-proxy 和平台 sandbox sources。 |
| Platform error | 这是 policy refusal、missing helper、unsupported OS capability,还是 backend limitation? | Platform-specific transform 和 setup sources。 |
第三部分到这里结束。Runtime 已经把模型提出的副作用带过 routing、governance、mutation protocols、hook/approval gates、retry semantics、permission compilation 和 platform containment。第四部分会从本地执行走向 clients 与 external runtimes:app-server、cloud tasks、memory 和 release machinery 都依赖同一条纪律,即 typed boundaries,而不是隐式副作用。
源码地图
| Evidence class | Claim | Source anchor |
|---|---|---|
| Verified source | Built-in profile resolution 会把 profile names 映射到 read-only、workspace-write 或 disabled profiles。 | codex-rs/core/src/config/permissions.rs |
| Verified source | Custom profiles 会在 platform transforms 之前编译 filesystem entries 和 network policy。 | codex-rs/core/src/config/permissions.rs |
| Verified source | Network sandbox policy 在 runtime policy 层只有 Restricted 和 Enabled 两种 case。 | codex-rs/protocol/src/permissions.rs |
| Verified source | Additional permissions 会被 normalize,并合并进 effective permission profile。 | codex-rs/sandboxing/src/policy_transforms.rs 和 policy_transforms.rs |
| Verified source | Sandbox type selection 区分 None、macOS Seatbelt、Linux seccomp 和 Windows restricted-token routes。 | codex-rs/sandboxing/src/manager.rs |
| Verified source | Initial sandbox selection 使用 tool preference、runtime policies、Windows level 和 managed-network requirements。 | codex-rs/sandboxing/src/manager.rs |
| Verified source | Sandbox attempt 会把 command、permissions、selected sandbox、network proxy state、cwd 和 platform options 交给 transform。 | codex-rs/core/src/tools/sandboxing.rs |
| Verified source | macOS transform 通过 /usr/bin/sandbox-exec 和生成的 policy sections 构造 Seatbelt command。 | codex-rs/sandboxing/src/manager.rs 和 seatbelt.rs |
| Verified source | macOS proxy configuration 和 managed-network enforcement 会强制进入 restricted-network branch;只有显式 loopback proxy ports 会添加 outbound allowances。 | codex-rs/sandboxing/src/seatbelt.rs |
| Verified source | Linux helper 会构造 bwrap filesystem/namespace state,应用 seccomp/no-new-privileges,再 exec command。 | codex-rs/linux-sandbox/src/linux_run_main.rs |
| Verified source | Linux proxy routing 会在 host 侧准备 loopback proxy routes,并在 namespace 内重写 proxy environment。 | codex-rs/linux-sandbox/src/proxy_routing.rs |
| Verified source | Windows sandbox setup 会把 roots、deny-write paths、proxy ports 和 local-binding behavior 带进 elevated setup。 | codex-rs/windows-sandbox-rs/src/setup.rs 和 setup.rs |
| Verified source | Windows spawn preparation 会选择 sandbox credentials、capability SIDs 和 ACL inputs;ACL helpers 会读取并更新 DACL/ACE state。 | codex-rs/windows-sandbox-rs/src/spawn_prep.rs 和 acl.rs |
| Verified source | Windows firewall setup 会配置 offline outbound 和 loopback proxy rules;WFP setup 会安装 account-scoped filters。 | firewall.rs, wfp_setup.rs, wfp.rs, 和 filter_specs.rs |
| Verified source | Windows legacy backend 会拒绝 restricted read-only access,并要求 elevated backend 执行该形状。 | codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs |
| Verified source | Limited network mode 允许 GET、HEAD、OPTIONS,但不允许 POST 或 CONNECT。 | codex-rs/network-proxy/src/policy.rs |
| Surrounding contract inference | 可移植抽象是一条 permission-profile-to-platform-transform pipeline;OS backends 不等价。 | 由上述 manager、policy transform、platform helper 和 proxy sources 综合得出。 |