SandboxPolicy选择
Codex 当前的 sandbox 选择并不是直接匹配旧式 SandboxPolicy 枚举。真正参与运行时决策的是 PermissionProfile 物化出的文件系统策略和网络策略、工具声明的 SandboxablePreference、单次调用的 SandboxPermissions、network policy owner、executor 是否自行管理进程 sandbox、managed network 要求以及平台能力。 SandboxManager 先回答“逻辑上是否需要平台 sandbox”,Orchestrator 再决定由当前 host 还是 executor 执行隔离,最后 transform 才把命令和权限转成平台 argv。
本文面向已经读过工具审批架构、网络审批与规则保存和 ToolOrchestrator执行流程的读者。前文解释审批和网络阻断,本文只研究 sandbox backend 的选择与命令变换,不展开 Seatbelt profile、Bubblewrap mount 或 Windows ACL 的全部实现。读完后,读者应能从 PermissionProfile 判断是否需要平台 sandbox,解释为什么 explicit escalation 仍可能保持 sandbox,并从 transform error 定位 平台、cwd、helper 或 managed network 问题。
1. 当前模型
1.1 Profile模型
PermissionProfile 是运行时安全输入的所有者。Managed profile 保存文件系统和网络策略;Disabled 表示文件系统不受 managed sandbox 约束且网络 enabled;External 表示隔离由外部环境负责,但仍保留网络策略。旧 SandboxPolicy 只通过转换函数进入 这个模型,不再直接驱动 backend 选择。
源码位置:codex-rs/protocol/src/models.rs :: PermissionProfile::from_runtime_permissions_with_enforcement
pub fn from_runtime_permissions_with_enforcement(
enforcement: SandboxEnforcement,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
) -> Self {
match file_system_sandbox_policy.kind {
FileSystemSandboxKind::ExternalSandbox => Self::External {
network: network_sandbox_policy,
},
FileSystemSandboxKind::Unrestricted
if enforcement == SandboxEnforcement::Disabled =>
{
Self::Disabled
}
FileSystemSandboxKind::Restricted | FileSystemSandboxKind::Unrestricted => {
Self::Managed {
file_system: ManagedFileSystemPermissions::from_sandbox_policy(
file_system_sandbox_policy,
),
network: network_sandbox_policy,
}
}
}
}1.2 运行时策略
to_runtime_permissions 将 profile 展开为 FileSystemSandboxPolicy 和 NetworkSandboxPolicy。文件系统 kind 有 Restricted、 Unrestricted、ExternalSandbox;Restricted 还包含 read/write/deny entries。网络策略则回答网络是否 enabled,或是否必须由 sandbox/managed proxy 限制。
源码位置:codex-rs/protocol/src/models.rs :: PermissionProfile::to_runtime_permissions
pub fn to_runtime_permissions(
&self,
) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) {
(
self.file_system_sandbox_policy(),
self.network_sandbox_policy(),
)
}2. 权限物化
2.1 Workspace roots
workspace-write preset 中的 :workspace_roots 是符号路径,不能直接交给 OS sandbox。Turn environment 在执行前调用 materialize_project_roots_with_workspace_roots,把符号条目按当前 environment 的 workspace roots 转成实际策略。远程和本地 environment 因此可以共享配置形状,却得到不同的 native enforcement。
源码位置:codex-rs/protocol/src/models.rs :: PermissionProfile::materialize_project_roots_with_workspace_roots
pub fn materialize_project_roots_with_workspace_roots(
self,
workspace_roots: &[AbsolutePathBuf],
) -> Self {
match self {
Self::Managed {
file_system,
network,
} => {
let file_system = file_system
.to_sandbox_policy()
.materialize_project_roots_with_workspace_roots(workspace_roots);
Self::Managed {
file_system: ManagedFileSystemPermissions::from_sandbox_policy(
&file_system,
),
network,
}
}
Self::Disabled => Self::Disabled,
Self::External { network } => Self::External { network },
}
}2.2 附加权限
单次调用的 additional permissions 不会替换基础 profile,而是由 effective_permission_profile 合并。文件系统权限通过 effective_file_system_sandbox_policy 合并,网络权限通过 effective_network_sandbox_policy 合并;最终 profile 保留原来的 enforcement 类型,External 不会因为一次网络授权变成 Managed。
源码位置:codex-rs/sandboxing/src/policy_transforms.rs :: effective_permission_profile
pub fn effective_permission_profile(
permission_profile: &PermissionProfile,
additional_permissions: Option<&AdditionalPermissionProfile>,
) -> PermissionProfile {
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,
)
}2.3 Deny条目
Additional permissions 可以增加读写范围,但不能删除基础 profile 的 deny-read。合并和 intersection 代码会保留仍然约束已接受 grant 的 deny entries;glob 形式只允许 Deny,避免把一个模糊 pattern 当成新的读写授权。
源码位置:codex-rs/sandboxing/src/policy_transforms.rs :: normalize_additional_permissions
if matches!(&entry.path, FileSystemPath::GlobPattern { .. })
&& entry.access != FileSystemAccessMode::Deny
{
return Err(
"glob file system permissions only support deny-read entries".to_string(),
);
}3. 是否需要Sandbox
3.1 工具偏好
SandboxablePreference 有 Auto、Require 和 Forbid。Shell、unified exec 与 apply patch 都使用 Auto;Require 强制逻辑需求, Forbid 则直接跳过平台 sandbox。偏好只回答工具层意图,不保证主机一定有对应 backend。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxablePreference
pub enum SandboxablePreference {
Auto,
Require,
Forbid,
}3.2 Auto判定
Auto 的核心函数是 should_require_platform_sandbox。Managed network 有要求时总是需要平台 sandbox;网络 restricted 时,除非 文件系统明确由 ExternalSandbox 负责,否则需要平台 sandbox;网络 enabled 时,只有 Restricted 且没有 full disk write 的文件 系统策略需要 sandbox。Restricted 并不自动等于 sandbox——如果策略实际给了全盘写权限,平台 wrapper不会增加有效隔离。
源码位置:codex-rs/sandboxing/src/policy_transforms.rs :: should_require_platform_sandbox
pub fn should_require_platform_sandbox(
file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
has_managed_network_requirements: bool,
) -> bool {
if has_managed_network_requirements {
return true;
}
if !network_policy.is_enabled() {
return !matches!(
file_system_policy.kind,
FileSystemSandboxKind::ExternalSandbox
);
}
match file_system_policy.kind {
FileSystemSandboxKind::Restricted => {
!file_system_policy.has_full_disk_write_access()
}
FileSystemSandboxKind::Unrestricted
| FileSystemSandboxKind::ExternalSandbox => false,
}
}3.3 主机映射
select_initial 先调用 should_sandbox,再调用 get_platform_sandbox。macOS 映射 Seatbelt,Linux 映射 LinuxSeccomp,Windows 只有 level 非 Disabled 时才映射 WindowsRestrictedToken。未知平台或关闭的 Windows sandbox 返回 None,最终降为 SandboxType::None。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::select_initial
pub fn select_initial(
&self,
permission_profile: &PermissionProfile,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
has_managed_network_requirements: bool,
) -> SandboxType {
if self.should_sandbox(
permission_profile,
pref,
has_managed_network_requirements,
) {
get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
)
.unwrap_or(SandboxType::None)
} else {
SandboxType::None
}
}这个 fallback 是能力降级,不是策略证明。诊断时必须分别问“策略是否要求 sandbox”和“主机是否选出了具体 backend”。
4. 首次尝试
4.1 Owner约束
Orchestrator 先识别环境是否携带 attachment-owned network policy。这类策略属于 environment owner,调用方不能用 RequireEscalated 绕过;即使文件系统没有 deny-read,首次 attempt 也不会获得 unsandboxed override。
源码位置:codex-rs/core/src/tools/orchestrator.rs :: ToolOrchestrator::run
let environment = tool.turn_environment(req);
let owner_network_policy = environment.config().network_policy.is_some();
if owner_network_policy
&& tool
.sandbox_permissions(req)
.requires_escalated_permissions()
{
return Err(ToolError::Rejected(
"attachment-owned network policy cannot be bypassed by sandbox escalation"
.to_string(),
));
}这条检查发生在审批之前。它不是“先询问用户是否允许绕过”,而是声明 owner policy 不属于当前调用可以升级的权限边界。
4.2 Executor隔离
Unified Exec 的 remote environment 或带 executor shell snapshot 的请求,以及 remote Apply Patch,会让 executor 自己管理进程 sandbox。此时 host 保留尚未物化的 permission profile,让 executor 使用自己的 native roots;逻辑上的 sandbox_requested 仍可为 true,但 host 侧 concrete SandboxType 固定为 None。
相关源码:
codex-rs/core/src/tools/sandboxing.rs :: ToolRuntime::uses_executor_managed_process_sandboxcodex-rs/core/src/tools/runtimes/unified_exec.rs :: UnifiedExecRuntime::uses_executor_managed_process_sandboxcodex-rs/core/src/tools/orchestrator.rs :: ToolOrchestrator::run
let executor_managed_process_sandbox =
tool.uses_executor_managed_process_sandbox(req);
let permission_profile = environment.permission_profile();
let permissions = if executor_managed_process_sandbox {
permission_profile.clone()
} else {
environment.permission_profile_with_workspace_roots()
};
let initial_sandbox = if sandbox_requested
&& !executor_managed_process_sandbox
{
self.sandbox.select_initial(
&permissions,
sandbox_preference,
turn_ctx.windows_sandbox_level,
managed_network_active,
)
} else {
SandboxType::None
};这里的 None 表示“host 不包装”,不能解释为“请求不需要 sandbox”。真正是否需要隔离保存在 sandbox_requested,执行权限原形 则通过 exec_server_permissions 继续传给 executor。
4.3 Override条件
显式 RequireEscalated 或 ExecPolicy Allow 可以请求首次绕过 sandbox,但必须同时满足两个条件:文件系统没有 deny-read,且环境 没有 owner network policy。deny-read 只能在 sandbox 内强制;owner policy 则属于 attachment 的不可绕过约束。
源码位置:codex-rs/core/src/tools/orchestrator.rs :: ToolOrchestrator::run
let unsandboxed_allowed =
!owner_network_policy
&& unsandboxed_execution_allowed(&file_system_sandbox_policy);
let sandbox_override = if unsandboxed_allowed {
sandbox_override_for_first_attempt(
tool.sandbox_permissions(req),
&requirement,
&file_system_sandbox_policy,
)
} else {
SandboxOverride::NoOverride
};sandbox_override_for_first_attempt 内部仍区分 ExecPolicy 的 bypass_sandbox 与显式 escalation;外层新增的 owner 检查决定该函数 是否有资格参与选择。
4.4 网络激活
普通环境沿用 turn_ctx.network.is_some()。owner policy 环境则单独计算:已启用 controller proxy 可以激活 managed network; 命令的 additional permissions 也可以把 owner 的 restricted network 扩展为 enabled。离线 attachment 不会因为 Turn 里存在 其他网络对象就自动联网。
源码位置:codex-rs/core/src/tools/orchestrator.rs :: ToolOrchestrator::run
let managed_network_active = if owner_network_policy {
turn_ctx
.config
.permissions
.network
.as_ref()
.is_some_and(NetworkProxySpec::enabled)
|| network_approval_spec.as_ref().is_some_and(|spec| {
effective_network_sandbox_policy(
permission_profile.network_sandbox_policy(),
spec.trigger.additional_permissions.as_ref(),
)
.is_enabled()
})
} else {
turn_ctx.network.is_some()
};4.5 Attempt对象
逻辑需求、host backend 与 executor 原始权限同时写入 SandboxAttempt。permissions 是当前执行边界使用的 profile; exec_server_permissions 保留 host 物化 workspace roots 之前的 canonical profile;enforce_managed_network 则记录这次 attempt 是否必须携带 managed network enforcement。
源码位置:codex-rs/core/src/tools/sandboxing.rs :: SandboxAttempt
pub(crate) struct SandboxAttempt<'a> {
pub sandbox: SandboxType,
pub sandbox_requested: bool,
pub permissions: &'a PermissionProfile,
pub exec_server_permissions: &'a PermissionProfile,
pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a PathUri,
pub(crate) workspace_roots: &'a [PathUri],
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub network_denial_cancellation_token: Option<CancellationToken>,
pub(crate) network_proxy: Option<&'a NetworkProxy>,
}5. 平台Backend
5.1 类型映射
平台映射只有四种结果。名称 LinuxSeccomp 是对外类型名,但 transform 内部会根据权限、managed proxy 和 use_legacy_landlock 决定 helper 参数以及是否需要 Bubblewrap 路径。
源码位置:codex-rs/sandboxing/src/manager.rs :: get_platform_sandbox
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
}
}5.2 macOS
Seatbelt 分支把 effective filesystem/network policy、policy cwd、managed network context、environment id 和 network proxy 交给 create_seatbelt_command_args_with_profile。SandboxManager::new 使用 Process profile; SandboxManager::for_file_system_helpers 使用更窄的 FileSystemHelper profile,避免文件系统 helper 继承普通进程不需要的读取面。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::for_file_system_helpers
pub fn for_file_system_helpers() -> Self {
Self {
#[cfg(target_os = "macos")]
seatbelt_profile: MacosSeatbeltProfile::FileSystemHelper,
}
}准备阶段现在还会区分文件系统错误和 proxy 错误。比如 writable root 的任一祖先包含 symlink 时,Seatbelt 拒绝构造 policy, 返回 SeatbeltPreparation;它不会被误标成网络代理失败。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::transform
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => {
let pending = pending_sandboxed_request?;
let (file_system_sandbox_policy, network_sandbox_policy) = pending
.effective_permission_profile
.to_runtime_permissions();
let mut args = create_seatbelt_command_args_with_profile(
CreateSeatbeltCommandArgsParams {
command: os_argv_to_strings(argv),
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd: pending.native_sandbox_policy_cwd.as_path(),
enforce_managed_network,
managed_network,
environment_id,
network,
extra_allow_unix_sockets: &[],
},
self.seatbelt_profile,
)
.map_err(|err| match err {
SeatbeltPreparationError::FileSystem(message) => {
SandboxTransformError::SeatbeltPreparation(message)
}
SeatbeltPreparationError::EnvironmentNetworkProxy(message) => {
SandboxTransformError::EnvironmentNetworkProxy(message)
}
})?;
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());
full_command.append(&mut args);
(full_command, None, Some(pending))
}5.3 Linux
Linux 分支要求 codex_linux_sandbox_exe。managed proxy 需要网络通路;WSL1 如果路径需要 Bubblewrap,会返回 Wsl1UnsupportedForBubblewrap,而不是静默降级。helper 路径还通过 arg0 override 保留 codex-linux-sandbox 身份,使 launcher 与 helper 路径分离。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::transform
SandboxType::LinuxSeccomp => {
let pending = pending_sandboxed_request?;
let exe = codex_linux_sandbox_exe
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
let allow_proxy_network = allow_network_for_proxy(enforce_managed_network);
#[cfg(target_os = "linux")]
ensure_linux_bubblewrap_is_supported(
&pending
.effective_permission_profile
.file_system_sandbox_policy(),
use_legacy_landlock,
allow_proxy_network,
is_wsl1(),
)?;
let mut args = create_linux_sandbox_command_args_for_permission_profile(
os_argv_to_strings(argv),
pending.native_command_cwd.as_path(),
&pending.effective_permission_profile,
pending.native_sandbox_policy_cwd.as_path(),
use_legacy_landlock,
allow_proxy_network,
);
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(os_string_to_command_component(
exe.as_os_str().to_owned(),
));
full_command.append(&mut args);
(
full_command,
Some(linux_sandbox_arg0_override(exe)),
Some(pending),
)
}5.4 Windows
Windows 的普通 transform 先保留原 argv 与 effective permission profile;如果该 attempt 强制 managed network,sandbox level 必须是 Elevated,RestrictedToken backend 会在包装前返回 WindowsSandboxPreparation。direct-spawn 路径随后调用 wrap_windows_sandbox_exec_request_for_direct_spawn,解析 helper、proxy restricting SID、elevated/restricted backend 和文件系统 overrides,再将 wrapper argv 写回 request。包装完成后 request.sandbox 设为 None,因为真正执行的是 wrapper,内层 sandbox 信息已经编码进参数。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::transform
#[cfg(target_os = "windows")]
SandboxType::WindowsRestrictedToken => {
if enforce_managed_network
&& windows_sandbox_level != WindowsSandboxLevel::Elevated
{
return Err(
SandboxTransformError::WindowsSandboxPreparation(
"managed networking requires the elevated Windows sandbox backend"
.to_string(),
),
);
}
(
os_argv_to_strings(argv),
None,
Some(pending_sandboxed_request?),
)
}direct-spawn 选择 elevated backend 时现在只依据 WindowsSandboxLevel;managed proxy 不再隐式把普通 restricted-token 请求升级成 elevated。换言之,选择阶段必须先给出 Elevated,包装阶段才会生成对应文件系统 overrides 和 proxy restricting SID。
6. Transform流水线
6.1 URI验证
只有实际 sandbox backend 需要 host-native cwd 时,PendingSandboxedExecRequest::new 才把 command cwd 与 policy cwd 从 PathUri 转为 AbsolutePathBuf。SandboxType::None 可以保留 foreign cwd,供 remote exec-server 消费;这避免本地主机拒绝一个只在远程 环境有意义的路径。
源码位置:codex-rs/sandboxing/src/manager.rs :: PendingSandboxedExecRequest::new
let native_command_cwd = command_cwd.to_abs_path().map_err(|source| {
SandboxTransformError::InvalidCommandCwd {
cwd: command_cwd.clone(),
source,
}
})?;
let native_sandbox_policy_cwd =
sandbox_policy_cwd.to_abs_path().map_err(|source| {
SandboxTransformError::InvalidSandboxPolicyCwd {
cwd: sandbox_policy_cwd.clone(),
source,
}
})?;6.2 CA可读根
managed MITM proxy 可能提供本地 CA trust bundle。transform 在生成平台 policy 之前,把该文件作为额外 readable root 合入 effective permission profile;否则 sandboxed TLS client 无法读取代理证书,managed network 会因为证书文件不可见而失败。
源码位置:codex-rs/sandboxing/src/manager.rs :: with_managed_mitm_ca_readable_root
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
let file_system_sandbox_policy = file_system_sandbox_policy
.with_additional_readable_roots(
sandbox_policy_cwd,
std::slice::from_ref(managed_mitm_ca_trust_bundle_path),
);
PermissionProfile::from_runtime_permissions_with_enforcement(
permission_profile.enforcement(),
&file_system_sandbox_policy,
network_sandbox_policy,
)6.3 输出请求
transform 的输出仍保留 permission profile、network proxy、environment id、Windows level 和 arg0 override。Core 的 ExecRequest::from_sandbox_exec_request 再加入 expiration、capture policy、workspace roots 和环境标记,最后才进入 spawn。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxManager::transform
Ok(SandboxExecRequest {
command: argv,
cwd: command.cwd,
sandbox_policy_cwd: sandbox_policy_cwd.clone(),
env: command.env,
network: network.cloned(),
network_environment_id: environment_id.map(str::to_string),
sandbox,
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
arg0: arg0_override,
})7. Network关系
7.1 网络限制
NetworkSandboxPolicy Restricted 在 Auto 模式下通常需要平台 sandbox;ExternalSandbox 是例外,因为外部环境已经承担网络隔离。 即使 PermissionProfile::Disabled,managed network requirements 也会强制逻辑 sandbox,从而确保代理环境和网络限制能被平台 backend 强制。owner network policy 则进一步限制 escalation:它既影响 managed_network_active 的计算,也直接禁止 RequireEscalated 绕过 attachment owner 的策略。
7.2 Escalated调用
managed_network_for_sandbox_permissions 在 RequireEscalated 时返回 None。unsandboxed escalation 不能继续声称由 managed proxy 强制网络策略;需要网络审批的情况会在 Orchestrator retry 路径单独处理。
源码位置:codex-rs/core/src/tools/sandboxing.rs :: managed_network_for_sandbox_permissions
pub(crate) fn managed_network_for_sandbox_permissions(
network: Option<&NetworkProxy>,
sandbox_permissions: SandboxPermissions,
) -> Option<&NetworkProxy> {
if sandbox_permissions.requires_escalated_permissions() {
None
} else {
network
}
}7.3 Environment标记
Core 将 sandbox transform 的 network policy 投影到环境。网络没有 enabled 时,写入 CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR=1;macOS Seatbelt 还写入 sandbox type 标记。这些变量是子进程可见提示,真正的隔离仍 来自 platform wrapper 或 external sandbox。
源码位置:codex-rs/core/src/sandboxing/mod.rs :: ExecRequest::from_sandbox_exec_request
let network_sandbox_policy = permission_profile.network_sandbox_policy();
if !network_sandbox_policy.is_enabled() {
env.insert(
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(),
);
}
#[cfg(target_os = "macos")]
if sandbox == SandboxType::MacosSeatbelt {
env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
}8. 升级与失败
8.1 Deny-read保留
RequireEscalated 通常意味着绕过 sandbox,但有 deny-read entries 时,sandbox_permissions_preserving_denied_reads 将它降回 UseDefault。审批扩大了执行权限,却不能删除已有的否定约束。
源码位置:codex-rs/core/src/tools/sandboxing.rs :: sandbox_permissions_preserving_denied_reads
if sandbox_permissions.requires_escalated_permissions()
&& !unsandboxed_execution_allowed(file_system_sandbox_policy)
{
SandboxPermissions::UseDefault
} else {
sandbox_permissions
}8.2 Transform错误
选择得到 SandboxType 并不保证 transform 成功。错误包括 command/policy cwd 不能转换、Linux helper 缺失、managed proxy 准备失败、Seatbelt 文件系统 policy 准备失败、WSL1 不支持 Bubblewrap、非 macOS 使用 Seatbelt,以及 Windows level/proxy/wrapper 准备失败。它们发生在 handler spawn 前,应按配置或平台错误定位,而不是误判为命令退出码。
源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxTransformError
pub enum SandboxTransformError {
InvalidCommandCwd {
cwd: PathUri,
source: io::Error,
},
InvalidSandboxPolicyCwd {
cwd: PathUri,
source: io::Error,
},
MissingLinuxSandboxExecutable,
EnvironmentNetworkProxy(String),
#[cfg(target_os = "macos")]
SeatbeltPreparation(String),
#[cfg(target_os = "linux")]
Wsl1UnsupportedForBubblewrap,
#[cfg(not(target_os = "macos"))]
SeatbeltUnavailable,
#[cfg(target_os = "windows")]
WindowsSandboxPreparation(String),
}SeatbeltPreparation 与 EnvironmentNetworkProxy 的拆分很重要:symlinked writable root、路径规范化或只读 carve-out 构造失败属于 文件系统 policy;只有 managed proxy 上下文生成失败才属于网络。上层可据此给出正确诊断。
8.3 WSL1判定
WSL1 只在实际需要 Bubblewrap 时拒绝。managed proxy 或非 legacy Landlock 且没有 full disk write 都会触发 Bubblewrap 需求; unrestricted filesystem、无代理并允许非 Bubblewrap 路径时可以继续。
源码位置:codex-rs/sandboxing/src/manager.rs :: ensure_linux_bubblewrap_is_supported
let requires_bubblewrap = allow_network_for_proxy
|| (!use_legacy_landlock
&& !file_system_sandbox_policy.has_full_disk_write_access());
if is_wsl1 && requires_bubblewrap {
return Err(SandboxTransformError::Wsl1UnsupportedForBubblewrap);
}
Ok(())9. 测试路径
9.1 选择矩阵
manager tests 验证 Disabled profile 且无 managed network 时选择 None;同一 profile 有 managed network 时选择当前平台 backend; Restricted filesystem 即使 network enabled 也选择平台 backend。测试把逻辑 predicate 和平台映射同时覆盖。
源码位置:codex-rs/sandboxing/src/manager_tests.rs :: danger_full_access_*
let sandbox = manager.select_initial(
&PermissionProfile::Disabled,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
/*has_managed_network_requirements*/ false,
);
assert_eq!(sandbox, SandboxType::None);
let expected = get_platform_sandbox(
/*windows_sandbox_enabled*/ false,
)
.unwrap_or(SandboxType::None);
let sandbox = manager.select_initial(
&PermissionProfile::Disabled,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
/*has_managed_network_requirements*/ true,
);
assert_eq!(sandbox, expected);9.2 Foreign cwd
unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_policy 构造当前主机无法原生解释的远程 cwd,并使用 SandboxType::None。断言 transform 保留 PathUri 和 unrestricted filesystem policy,证明 URI native conversion 是 sandboxed backend 的要求,不是所有请求的前置条件。
源码位置:codex-rs/sandboxing/src/manager_tests.rs :: unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_policy
assert_eq!(exec_request.cwd, cwd_uri);
assert_eq!(exec_request.sandbox_policy_cwd, cwd_uri);
assert_eq!(
exec_request.permission_profile.file_system_sandbox_policy(),
FileSystemSandboxPolicy::unrestricted()
);
assert_eq!(
exec_request.permission_profile.network_sandbox_policy(),
NetworkSandboxPolicy::Restricted
);9.3 Additional权限
transform_additional_permissions_preserves_denied_entries 在基础 Restricted policy 中放入 deny path,再请求额外允许另一个目录。 断言 effective profile 同时包含新 grant 和原 deny,证明权限合并不会用“更宽权限”覆盖否定规则。
9.4 Seatbelt准备
macOS test 构造一个指向真实目录的 symlink workspace,并把它作为 writable root。transform 必须返回 SeatbeltPreparation,错误文本说明 symlinked writable roots 不受支持;断言还确认该错误没有被归类为 network proxy。
源码位置:codex-rs/sandboxing/src/manager_tests.rs :: symlinked_workspace_reports_seatbelt_preparation_error
assert!(matches!(
&error,
SandboxTransformError::SeatbeltPreparation(message)
if message.contains("symlinked writable roots are not supported")
));
assert!(!error.to_string().contains("network proxy"));它验证的是 policy 构造阶段的路径防护和错误分类,不证明运行后的 Seatbelt kernel enforcement。
9.5 WSL1与arg0
Linux tests 分别验证 WSL1 在需要 Bubblewrap 时返回专用错误、legacy Landlock 非 Bubblewrap 路径可以继续,以及 helper 路径是否 正确保存在 arg0 override。它们不证明实际 kernel seccomp/namespace 强制效果,只证明选择与 argv 生成。
源码位置:codex-rs/sandboxing/src/manager_tests.rs :: wsl1_rejects_linux_bubblewrap_path
assert!(matches!(
super::ensure_linux_bubblewrap_is_supported(
&restricted_policy,
/*use_legacy_landlock*/ false,
/*allow_network_for_proxy*/ false,
/*is_wsl1*/ true,
),
Err(super::SandboxTransformError::Wsl1UnsupportedForBubblewrap)
));9.6 Deny-read升级
Core test 使用 **/*.env deny glob 和 RequireEscalated,断言首次 override 仍是 NoOverride,权限被降为 UseDefault;同样, ExecPolicy allow 的 bypass 也不能移除 deny-read。该测试覆盖审批后最容易被误解的边界。
源码位置:codex-rs/core/src/tools/sandboxing_tests.rs :: deny_read_blocks_explicit_escalation_and_policy_bypass
assert_eq!(
sandbox_override_for_first_attempt(
SandboxPermissions::RequireEscalated,
&ExecApprovalRequirement::Skip {
bypass_sandbox: false,
proposed_execpolicy_amendment: None,
},
&file_system_policy,
),
SandboxOverride::NoOverride,
);
assert!(!unsandboxed_execution_allowed(&file_system_policy));
assert_eq!(
sandbox_permissions_preserving_denied_reads(
SandboxPermissions::RequireEscalated,
&file_system_policy,
),
SandboxPermissions::UseDefault,
);10. 阅读实践
在源码仓库根目录运行定向测试:
RUST_MIN_STACK=16777216 cargo test -p codex-sandboxing danger_full_access
RUST_MIN_STACK=16777216 cargo test -p codex-sandboxing restricted_file_system_uses_platform_sandbox
RUST_MIN_STACK=16777216 cargo test -p codex-sandboxing unsandboxed_transform_preserves_foreign_cwd
RUST_MIN_STACK=16777216 cargo test -p codex-sandboxing symlinked_workspace_reports_seatbelt_preparation_error
RUST_MIN_STACK=16777216 cargo test -p codex-core deny_read_blocks_explicit_escalation_and_policy_bypass然后复述三个场景:
- executor-managed request 的
sandbox_requested为 true 时,为什么 hostSandboxType仍是 None?真正的权限在哪里保留? - RequireEscalated 已获批准,但环境带 owner network policy。为什么 Orchestrator 在审批前拒绝,而不是进入 unsandboxed retry?
- macOS symlinked writable root 与 managed proxy 准备失败分别映射到哪种错误?Windows managed network 又要求哪个 backend?
能够从 PermissionProfile::to_runtime_permissions 走到 should_require_platform_sandbox、SandboxManager::select_initial、 uses_executor_managed_process_sandbox、sandbox_override_for_first_attempt 和 SandboxManager::transform,就掌握了 sandbox 从 策略 owner 到执行边界和 argv 的真实选择链。进一步分析 具体平台强制机制时,应继续以这里生成的 effective permission profile 和 transform 参数作为入口,而不是回到已经转换完成的 legacy SandboxPolicy 名称猜测行为。
