linux-sandbox: plumb split sandbox policies through helper

This commit is contained in:
Michael Bolin
2026-03-06 15:19:04 -08:00
parent 7b5b968483
commit bddefc9edd
6 changed files with 313 additions and 47 deletions
+90 -23
View File
@@ -3,6 +3,7 @@ use crate::spawn::SpawnChildRequest;
use crate::spawn::StdioPolicy;
use crate::spawn::spawn_child_async;
use codex_network_proxy::NetworkProxy;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use std::collections::HashMap;
use std::path::Path;
@@ -14,9 +15,9 @@ use tokio::process::Child;
/// isolation plus seccomp for network restrictions.
///
/// Unlike macOS Seatbelt where we directly embed the policy text, the Linux
/// helper accepts a list of `--sandbox-permission`/`-s` flags mirroring the
/// public CLI. We convert the internal [`SandboxPolicy`] representation into
/// the equivalent CLI options.
/// helper is a separate executable. We pass the legacy [`SandboxPolicy`] plus
/// split filesystem/network policies as JSON so the helper can migrate
/// incrementally without breaking older call sites.
#[allow(clippy::too_many_arguments)]
pub async fn spawn_command_under_linux_sandbox<P>(
codex_linux_sandbox_exe: P,
@@ -32,9 +33,13 @@ pub async fn spawn_command_under_linux_sandbox<P>(
where
P: AsRef<Path>,
{
let args = create_linux_sandbox_command_args(
let file_system_sandbox_policy = FileSystemSandboxPolicy::from(sandbox_policy);
let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy);
let args = create_linux_sandbox_command_args_for_policies(
command,
sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd,
use_bwrap_sandbox,
allow_network_for_proxy(false),
@@ -45,7 +50,7 @@ where
args,
arg0,
cwd: command_cwd,
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
network_sandbox_policy,
network,
stdio_policy,
env,
@@ -60,13 +65,55 @@ pub(crate) fn allow_network_for_proxy(enforce_managed_network: bool) -> bool {
enforce_managed_network
}
/// Converts the sandbox policy into the CLI invocation for `codex-linux-sandbox`.
/// Converts the sandbox policies into the CLI invocation for
/// `codex-linux-sandbox`.
///
/// The helper performs the actual sandboxing (bubblewrap + seccomp) after
/// parsing these arguments. See `docs/linux_sandbox.md` for the Linux semantics.
pub(crate) fn create_linux_sandbox_command_args(
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_linux_sandbox_command_args_for_policies(
command: Vec<String>,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
#[expect(clippy::expect_used)]
let sandbox_policy_json =
serde_json::to_string(sandbox_policy).expect("Failed to serialize SandboxPolicy to JSON");
#[expect(clippy::expect_used)]
let file_system_policy_json = serde_json::to_string(file_system_sandbox_policy)
.expect("Failed to serialize FileSystemSandboxPolicy to JSON");
#[expect(clippy::expect_used)]
let network_policy_json = serde_json::to_string(&network_sandbox_policy)
.expect("Failed to serialize NetworkSandboxPolicy to JSON");
let mut linux_cmd = create_linux_sandbox_command_args(
command,
sandbox_policy_cwd,
use_bwrap_sandbox,
allow_network_for_proxy,
);
linux_cmd.splice(
2..2,
[
"--sandbox-policy".to_string(),
sandbox_policy_json,
"--file-system-sandbox-policy".to_string(),
file_system_policy_json,
"--network-sandbox-policy".to_string(),
network_policy_json,
],
);
linux_cmd
}
/// Converts the sandbox cwd and execution options into the CLI invocation for
/// `codex-linux-sandbox`.
pub(crate) fn create_linux_sandbox_command_args(
command: Vec<String>,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
allow_network_for_proxy: bool,
@@ -77,16 +124,7 @@ pub(crate) fn create_linux_sandbox_command_args(
.expect("cwd must be valid UTF-8")
.to_string();
#[expect(clippy::expect_used)]
let sandbox_policy_json =
serde_json::to_string(sandbox_policy).expect("Failed to serialize SandboxPolicy to JSON");
let mut linux_cmd: Vec<String> = vec![
"--sandbox-policy-cwd".to_string(),
sandbox_policy_cwd,
"--sandbox-policy".to_string(),
sandbox_policy_json,
];
let mut linux_cmd: Vec<String> = vec!["--sandbox-policy-cwd".to_string(), sandbox_policy_cwd];
if use_bwrap_sandbox {
linux_cmd.push("--use-bwrap-sandbox".to_string());
}
@@ -113,16 +151,14 @@ mod tests {
fn bwrap_flags_are_feature_gated() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let policy = SandboxPolicy::new_read_only_policy();
let with_bwrap =
create_linux_sandbox_command_args(command.clone(), &policy, cwd, true, false);
let with_bwrap = create_linux_sandbox_command_args(command.clone(), 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, false);
let without_bwrap = create_linux_sandbox_command_args(command, cwd, false, false);
assert_eq!(
without_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
false
@@ -133,15 +169,46 @@ mod tests {
fn proxy_flag_is_included_when_requested() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let policy = SandboxPolicy::new_read_only_policy();
let args = create_linux_sandbox_command_args(command, &policy, cwd, true, true);
let args = create_linux_sandbox_command_args(command, cwd, true, true);
assert_eq!(
args.contains(&"--allow-network-for-proxy".to_string()),
true
);
}
#[test]
fn split_policy_flags_are_included() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let file_system_sandbox_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
let args = create_linux_sandbox_command_args_for_policies(
command,
&sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
cwd,
true,
false,
);
assert_eq!(
args.windows(2).any(|window| {
window[0] == "--file-system-sandbox-policy" && !window[1].is_empty()
}),
true
);
assert_eq!(
args.windows(2)
.any(|window| window[0] == "--network-sandbox-policy"
&& window[1] == "\"restricted\""),
true
);
}
#[test]
fn proxy_network_requires_managed_requirements() {
assert_eq!(allow_network_for_proxy(false), false);
+4 -2
View File
@@ -14,7 +14,7 @@ use crate::exec::SandboxType;
use crate::exec::StdoutStream;
use crate::exec::execute_exec_request;
use crate::landlock::allow_network_for_proxy;
use crate::landlock::create_linux_sandbox_command_args;
use crate::landlock::create_linux_sandbox_command_args_for_policies;
use crate::protocol::SandboxPolicy;
#[cfg(target_os = "macos")]
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
@@ -513,9 +513,11 @@ impl SandboxManager {
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(
let mut args = create_linux_sandbox_command_args_for_policies(
command.clone(),
&effective_policy,
&effective_file_system_policy,
effective_network_policy,
sandbox_policy_cwd,
use_linux_sandbox_bwrap,
allow_proxy_network,