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 authored and GitHub committed 2026-02-10 07:44:21 +00:00
1 parent b61ea47e83
commit 3391e5ea86
24 files changed
+1046 -122

No files matched your search

+42 -6
View File
@@ -1,6 +1,8 @@
use crate::protocol::SandboxPolicy;
use crate::spawn::SpawnChildRequest;
use crate::spawn::StdioPolicy;
use crate::spawn::spawn_child_async;
use codex_network_proxy::NetworkProxy;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
@@ -23,6 +25,7 @@ pub async fn spawn_command_under_linux_sandbox<P>(
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
stdio_policy: StdioPolicy,
network: Option<&NetworkProxy>,
env: HashMap<String, String>,
) -> std::io::Result<Child>
where
@@ -33,20 +36,29 @@ where
sandbox_policy,
sandbox_policy_cwd,
use_bwrap_sandbox,
allow_network_for_proxy(false),
);
let arg0 = Some("codex-linux-sandbox");
spawn_child_async(
codex_linux_sandbox_exe.as_ref().to_path_buf(),
spawn_child_async(SpawnChildRequest {
program: codex_linux_sandbox_exe.as_ref().to_path_buf(),
args,
arg0,
command_cwd,
cwd: command_cwd,
sandbox_policy,
network,
stdio_policy,
env,
)
})
.await
}
pub(crate) fn allow_network_for_proxy(enforce_managed_network: bool) -> bool {
// When managed network requirements are active, request proxy-only
// networking from the Linux sandbox helper. Without managed requirements,
// preserve existing behavior.
enforce_managed_network
}
/// Converts the sandbox policy into the CLI invocation for `codex-linux-sandbox`.
///
/// The helper performs the actual sandboxing (bubblewrap + seccomp) after
@@ -56,6 +68,7 @@ pub(crate) fn create_linux_sandbox_command_args(
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
#[expect(clippy::expect_used)]
let sandbox_policy_cwd = sandbox_policy_cwd
@@ -76,6 +89,9 @@ pub(crate) fn create_linux_sandbox_command_args(
if use_bwrap_sandbox {
linux_cmd.push("--use-bwrap-sandbox".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
}
// Separator so that command arguments starting with `-` are not parsed as
// options of the helper itself.
@@ -98,16 +114,36 @@ mod tests {
let cwd = Path::new("/tmp");
let policy = SandboxPolicy::ReadOnly;
let with_bwrap = create_linux_sandbox_command_args(command.clone(), &policy, cwd, true);
let with_bwrap =
create_linux_sandbox_command_args(command.clone(), &policy, cwd, true, false);
assert_eq!(
with_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
true
);
let without_bwrap = create_linux_sandbox_command_args(command, &policy, cwd, false);
let without_bwrap = create_linux_sandbox_command_args(command, &policy, cwd, false, false);
assert_eq!(
without_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
false
);
}
#[test]
fn proxy_flag_is_included_when_requested() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let policy = SandboxPolicy::ReadOnly;
let args = create_linux_sandbox_command_args(command, &policy, cwd, true, true);
assert_eq!(
args.contains(&"--allow-network-for-proxy".to_string()),
true
);
}
#[test]
fn proxy_network_requires_managed_requirements() {
assert_eq!(allow_network_for_proxy(false), false);
assert_eq!(allow_network_for_proxy(true), true);
}
}