feat(sandbox): enforce proxy-aware network routing in sandbox (#11113)

## Summary
- expand proxy env injection to cover common tool env vars
(`HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY` families +
tool-specific variants)
- harden macOS Seatbelt network policy generation to route through
inferred loopback proxy endpoints and fail closed when proxy env is
malformed
- thread proxy-aware Linux sandbox flags and add minimal bwrap netns
isolation hook for restricted non-proxy runs
- add/refresh tests for proxy env wiring, Seatbelt policy generation,
and Linux sandbox argument wiring
This commit is contained in:
viyatb-oai
2026-02-09 23:44:21 -08:00
committed by GitHub
Unverified
parent b61ea47e83
commit 3391e5ea86
24 changed files with 1046 additions and 122 deletions
+68 -10
View File
@@ -1,7 +1,7 @@
/*
Module: sandboxing
Build platform wrappers and produce ExecEnv for execution. Owns lowlevel
Build platform wrappers and produce ExecRequest for execution. Owns low-level
sandbox placement and transformation of portable CommandSpec into a
readytospawn environment.
*/
@@ -11,6 +11,7 @@ use crate::exec::ExecToolCallOutput;
use crate::exec::SandboxType;
use crate::exec::StdoutStream;
use crate::exec::execute_exec_env;
use crate::landlock::allow_network_for_proxy;
use crate::landlock::create_linux_sandbox_command_args;
use crate::protocol::SandboxPolicy;
#[cfg(target_os = "macos")]
@@ -40,10 +41,11 @@ pub struct CommandSpec {
}
#[derive(Debug)]
pub struct ExecEnv {
pub struct ExecRequest {
pub command: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub expiration: ExecExpiration,
pub sandbox: SandboxType,
pub windows_sandbox_level: WindowsSandboxLevel,
@@ -59,6 +61,10 @@ pub(crate) struct SandboxTransformRequest<'a> {
pub spec: CommandSpec,
pub policy: &'a SandboxPolicy,
pub sandbox: SandboxType,
pub enforce_managed_network: bool,
// TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>>
// to make shared ownership explicit across runtime/sandbox plumbing.
pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a Path,
pub codex_linux_sandbox_exe: Option<&'a PathBuf>,
pub use_linux_sandbox_bwrap: bool,
@@ -93,6 +99,7 @@ impl SandboxManager {
policy: &SandboxPolicy,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
has_managed_network_requirements: bool,
) -> SandboxType {
match pref {
SandboxablePreference::Forbid => SandboxType::None,
@@ -106,7 +113,14 @@ impl SandboxManager {
}
SandboxablePreference::Auto => match policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
SandboxType::None
if has_managed_network_requirements {
crate::safety::get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
)
.unwrap_or(SandboxType::None)
} else {
SandboxType::None
}
}
_ => crate::safety::get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
@@ -119,11 +133,13 @@ impl SandboxManager {
pub(crate) fn transform(
&self,
request: SandboxTransformRequest<'_>,
) -> Result<ExecEnv, SandboxTransformError> {
) -> Result<ExecRequest, SandboxTransformError> {
let SandboxTransformRequest {
mut spec,
policy,
sandbox,
enforce_managed_network,
network,
sandbox_policy_cwd,
codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
@@ -147,8 +163,13 @@ impl SandboxManager {
SandboxType::MacosSeatbelt => {
let mut seatbelt_env = HashMap::new();
seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let mut args =
create_seatbelt_command_args(command.clone(), policy, sandbox_policy_cwd);
let mut args = create_seatbelt_command_args(
command.clone(),
policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
);
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);
@@ -159,11 +180,13 @@ impl SandboxManager {
SandboxType::LinuxSeccomp => {
let exe = codex_linux_sandbox_exe
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
let allow_proxy_network = allow_network_for_proxy(enforce_managed_network);
let mut args = create_linux_sandbox_command_args(
command.clone(),
policy,
sandbox_policy_cwd,
use_linux_sandbox_bwrap,
allow_proxy_network,
);
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(exe.to_string_lossy().to_string());
@@ -186,10 +209,11 @@ impl SandboxManager {
env.extend(sandbox_env);
Ok(ExecEnv {
Ok(ExecRequest {
command,
cwd: spec.cwd,
env,
network: network.cloned(),
expiration: spec.expiration,
sandbox,
windows_sandbox_level,
@@ -205,10 +229,44 @@ impl SandboxManager {
}
pub async fn execute_env(
env: ExecEnv,
env: ExecRequest,
policy: &SandboxPolicy,
network: Option<NetworkProxy>,
stdout_stream: Option<StdoutStream>,
) -> crate::error::Result<ExecToolCallOutput> {
execute_exec_env(env, policy, stdout_stream, network).await
execute_exec_env(env, policy, stdout_stream).await
}
#[cfg(test)]
mod tests {
use super::SandboxManager;
use crate::exec::SandboxType;
use crate::protocol::SandboxPolicy;
use crate::tools::sandboxing::SandboxablePreference;
use codex_protocol::config_types::WindowsSandboxLevel;
use pretty_assertions::assert_eq;
#[test]
fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
let manager = SandboxManager::new();
let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
false,
);
assert_eq!(sandbox, SandboxType::None);
}
#[test]
fn danger_full_access_uses_platform_sandbox_with_network_requirements() {
let manager = SandboxManager::new();
let expected = crate::safety::get_platform_sandbox(false).unwrap_or(SandboxType::None);
let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
true,
);
assert_eq!(sandbox, expected);
}
}