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
parent b61ea47e83
commit 3391e5ea86
24 changed files with 1046 additions and 122 deletions

View File

@@ -6,6 +6,7 @@ use std::os::fd::FromRawFd;
use std::path::Path;
use std::path::PathBuf;
use crate::bwrap::BwrapNetworkMode;
use crate::bwrap::BwrapOptions;
use crate::bwrap::create_bwrap_command_args;
use crate::landlock::apply_sandbox_policy_to_current_thread;
@@ -40,6 +41,14 @@ pub struct LandlockCommand {
#[arg(long = "apply-seccomp-then-exec", hide = true, default_value_t = false)]
pub apply_seccomp_then_exec: bool,
/// Internal compatibility flag.
///
/// By default, restricted-network sandboxing uses isolated networking.
/// If set, sandbox setup switches to proxy-only network mode
/// (currently enforced the same as isolated networking).
#[arg(long = "allow-network-for-proxy", hide = true, default_value_t = false)]
pub allow_network_for_proxy: bool,
/// When set, skip mounting a fresh `/proc` even though PID isolation is
/// still enabled. This is primarily intended for restrictive container
/// environments that deny `--proc /proc`.
@@ -64,6 +73,7 @@ pub fn run_main() -> ! {
sandbox_policy,
use_bwrap_sandbox,
apply_seccomp_then_exec,
allow_network_for_proxy,
no_proc,
command,
} = LandlockCommand::parse();
@@ -75,18 +85,24 @@ pub fn run_main() -> ! {
// Inner stage: apply seccomp/no_new_privs after bubblewrap has already
// established the filesystem view.
if apply_seccomp_then_exec {
if let Err(e) =
apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false)
{
if let Err(e) = apply_sandbox_policy_to_current_thread(
&sandbox_policy,
&sandbox_policy_cwd,
false,
allow_network_for_proxy,
) {
panic!("error applying Linux sandbox restrictions: {e:?}");
}
exec_or_panic(command);
}
if sandbox_policy.has_full_disk_write_access() {
if let Err(e) =
apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false)
{
if sandbox_policy.has_full_disk_write_access() && !allow_network_for_proxy {
if let Err(e) = apply_sandbox_policy_to_current_thread(
&sandbox_policy,
&sandbox_policy_cwd,
false,
allow_network_for_proxy,
) {
panic!("error applying Linux sandbox restrictions: {e:?}");
}
exec_or_panic(command);
@@ -100,15 +116,25 @@ pub fn run_main() -> ! {
&sandbox_policy_cwd,
&sandbox_policy,
use_bwrap_sandbox,
allow_network_for_proxy,
command,
);
run_bwrap_with_proc_fallback(&sandbox_policy_cwd, &sandbox_policy, inner, !no_proc);
run_bwrap_with_proc_fallback(
&sandbox_policy_cwd,
&sandbox_policy,
inner,
!no_proc,
allow_network_for_proxy,
);
}
// Legacy path: Landlock enforcement only, when bwrap sandboxing is not enabled.
if let Err(e) =
apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, true)
{
if let Err(e) = apply_sandbox_policy_to_current_thread(
&sandbox_policy,
&sandbox_policy_cwd,
true,
allow_network_for_proxy,
) {
panic!("error applying legacy Linux sandbox restrictions: {e:?}");
}
exec_or_panic(command);
@@ -119,6 +145,7 @@ fn run_bwrap_with_proc_fallback(
sandbox_policy: &codex_core::protocol::SandboxPolicy,
inner: Vec<String>,
mount_proc: bool,
allow_network_for_proxy: bool,
) -> ! {
let mut mount_proc = mount_proc;
@@ -127,11 +154,28 @@ fn run_bwrap_with_proc_fallback(
mount_proc = false;
}
let options = BwrapOptions { mount_proc };
let network_mode = bwrap_network_mode(sandbox_policy, allow_network_for_proxy);
let options = BwrapOptions {
mount_proc,
network_mode,
};
let argv = build_bwrap_argv(inner, sandbox_policy, sandbox_policy_cwd, options);
exec_vendored_bwrap(argv);
}
fn bwrap_network_mode(
sandbox_policy: &codex_core::protocol::SandboxPolicy,
allow_network_for_proxy: bool,
) -> BwrapNetworkMode {
if allow_network_for_proxy {
BwrapNetworkMode::ProxyOnly
} else if sandbox_policy.has_full_network_access() {
BwrapNetworkMode::FullAccess
} else {
BwrapNetworkMode::Isolated
}
}
fn build_bwrap_argv(
inner: Vec<String>,
sandbox_policy: &codex_core::protocol::SandboxPolicy,
@@ -164,7 +208,10 @@ fn preflight_proc_mount_support(
preflight_command,
sandbox_policy,
sandbox_policy_cwd,
BwrapOptions { mount_proc: true },
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::FullAccess,
},
);
let stderr = run_bwrap_in_child_capture_stderr(preflight_argv);
!is_proc_mount_failure(stderr.as_str())
@@ -268,6 +315,7 @@ fn build_inner_seccomp_command(
sandbox_policy_cwd: &Path,
sandbox_policy: &codex_core::protocol::SandboxPolicy,
use_bwrap_sandbox: bool,
allow_network_for_proxy: bool,
command: Vec<String>,
) -> Vec<String> {
let current_exe = match std::env::current_exe() {
@@ -290,6 +338,9 @@ fn build_inner_seccomp_command(
inner.push("--use-bwrap-sandbox".to_string());
inner.push("--apply-seccomp-then-exec".to_string());
}
if allow_network_for_proxy {
inner.push("--allow-network-for-proxy".to_string());
}
inner.push("--".to_string());
inner.extend(command);
inner
@@ -342,7 +393,10 @@ mod tests {
vec!["/bin/true".to_string()],
&SandboxPolicy::ReadOnly,
Path::new("/"),
BwrapOptions { mount_proc: true },
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::FullAccess,
},
);
assert_eq!(
argv,
@@ -366,4 +420,38 @@ mod tests {
]
);
}
#[test]
fn inserts_unshare_net_when_network_isolation_requested() {
let argv = build_bwrap_argv(
vec!["/bin/true".to_string()],
&SandboxPolicy::ReadOnly,
Path::new("/"),
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::Isolated,
},
);
assert_eq!(argv.contains(&"--unshare-net".to_string()), true);
}
#[test]
fn inserts_unshare_net_when_proxy_only_network_mode_requested() {
let argv = build_bwrap_argv(
vec!["/bin/true".to_string()],
&SandboxPolicy::ReadOnly,
Path::new("/"),
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::ProxyOnly,
},
);
assert_eq!(argv.contains(&"--unshare-net".to_string()), true);
}
#[test]
fn proxy_only_mode_takes_precedence_over_full_network_policy() {
let mode = bwrap_network_mode(&SandboxPolicy::DangerFullAccess, true);
assert_eq!(mode, BwrapNetworkMode::ProxyOnly);
}
}