Skip to content

Commit e6db1a9

Browse files
authored
linux-sandbox: switch helper plumbing to PermissionProfile (#20106)
## Why `PermissionProfile` is the canonical runtime permission model in the Rust workspace, but the Linux sandbox helper still accepted a legacy `SandboxPolicy` plus separate filesystem and network policy flags. That translation layer made the helper interface harder to reason about and left `linux-sandbox`-specific callers and tests coupled to the legacy policy representation. This change moves the helper onto `PermissionProfile` directly so the Linux sandbox plumbing matches the rest of the permission stack. ## What changed - changed `codex-linux-sandbox` to accept `--permission-profile` and derive the runtime filesystem and network policies internally - updated the in-process seccomp and legacy Landlock path in `codex-rs/linux-sandbox` to operate on `PermissionProfile` - updated Linux sandbox argv construction in `codex-rs/sandboxing`, `codex-rs/core`, and the CLI debug sandbox path to pass the canonical profile instead of serializing compatibility policy projections - simplified the Linux sandbox tests to build the exact permission profile under test, including the managed-proxy path and direct-runtime-enforcement carveout coverage - removed helper-local `SandboxPolicy` usage from `bwrap` tests where `FileSystemSandboxPolicy` is already the value being exercised ## Testing - `cargo test -p codex-sandboxing` - `cargo test -p codex-linux-sandbox` (on this macOS host, the crate compiled cleanly and its Linux-only tests were cfg-gated) - `cargo test -p codex-core --no-run` - `cargo test -p codex-cli --no-run`
1 parent 80fb070 commit e6db1a9

11 files changed

Lines changed: 201 additions & 518 deletions

File tree

codex-rs/cli/src/debug_sandbox.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
1616
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
1717
use codex_protocol::config_types::SandboxMode;
1818
use codex_protocol::permissions::NetworkSandboxPolicy;
19-
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
19+
use codex_sandboxing::landlock::allow_network_for_proxy;
20+
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
2021
#[cfg(target_os = "macos")]
2122
use codex_sandboxing::seatbelt::CreateSeatbeltCommandArgsParams;
2223
#[cfg(target_os = "macos")]
@@ -222,19 +223,14 @@ async fn run_command_under_sandbox(
222223
.codex_linux_sandbox_exe
223224
.expect("codex-linux-sandbox executable not found");
224225
let use_legacy_landlock = config.features.use_legacy_landlock();
225-
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
226226
let network_sandbox_policy = config.permissions.network_sandbox_policy();
227-
let args = create_linux_sandbox_command_args_for_policies(
227+
let args = create_linux_sandbox_command_args_for_permission_profile(
228228
command,
229229
cwd.as_path(),
230-
&config
231-
.permissions
232-
.legacy_sandbox_policy(sandbox_policy_cwd.as_path()),
233-
&file_system_sandbox_policy,
234-
network_sandbox_policy,
230+
&config.permissions.permission_profile(),
235231
sandbox_policy_cwd.as_path(),
236232
use_legacy_landlock,
237-
/*allow_network_for_proxy*/ false,
233+
allow_network_for_proxy(managed_network_requirements_enabled),
238234
);
239235
spawn_debug_sandbox_child(
240236
codex_linux_sandbox_exe,

codex-rs/core/src/landlock.rs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ use crate::spawn::StdioPolicy;
33
use crate::spawn::spawn_child_async;
44
use codex_network_proxy::NetworkProxy;
55
use codex_protocol::models::PermissionProfile;
6-
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
76
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
87
use codex_sandboxing::landlock::allow_network_for_proxy;
9-
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
8+
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
109
use codex_utils_absolute_path::AbsolutePathBuf;
1110
use std::collections::HashMap;
1211
use std::path::Path;
@@ -17,9 +16,8 @@ use tokio::process::Child;
1716
/// isolation plus seccomp for network restrictions.
1817
///
1918
/// Unlike macOS Seatbelt where we directly embed the policy text, the Linux
20-
/// helper is a separate executable. We pass both the canonical split
21-
/// filesystem/network policies and a compatibility legacy projection as JSON
22-
/// until the helper protocol no longer needs the legacy field.
19+
/// helper is a separate executable. We pass the canonical permission profile
20+
/// as JSON and let the helper derive the runtime filesystem/network policies.
2321
#[allow(clippy::too_many_arguments)]
2422
pub async fn spawn_command_under_linux_sandbox<P>(
2523
codex_linux_sandbox_exe: P,
@@ -35,20 +33,11 @@ pub async fn spawn_command_under_linux_sandbox<P>(
3533
where
3634
P: AsRef<Path>,
3735
{
38-
let (file_system_sandbox_policy, network_sandbox_policy) =
39-
permission_profile.to_runtime_permissions();
40-
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
41-
permission_profile,
42-
&file_system_sandbox_policy,
43-
network_sandbox_policy,
44-
sandbox_policy_cwd.as_path(),
45-
);
46-
let args = create_linux_sandbox_command_args_for_policies(
36+
let network_sandbox_policy = permission_profile.network_sandbox_policy();
37+
let args = create_linux_sandbox_command_args_for_permission_profile(
4738
command,
4839
command_cwd.as_path(),
49-
&sandbox_policy,
50-
&file_system_sandbox_policy,
51-
network_sandbox_policy,
40+
permission_profile,
5241
sandbox_policy_cwd,
5342
use_legacy_landlock,
5443
allow_network_for_proxy(/*enforce_managed_network*/ false),

codex-rs/linux-sandbox/src/bwrap.rs

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,6 @@ mod tests {
10361036
use codex_protocol::protocol::FileSystemSandboxEntry;
10371037
use codex_protocol::protocol::FileSystemSandboxPolicy;
10381038
use codex_protocol::protocol::FileSystemSpecialPath;
1039-
use codex_protocol::protocol::SandboxPolicy;
10401039
use codex_utils_absolute_path::AbsolutePathBuf;
10411040
use pretty_assertions::assert_eq;
10421041
use tempfile::TempDir;
@@ -1066,7 +1065,7 @@ mod tests {
10661065
let command = vec!["/bin/true".to_string()];
10671066
let args = create_bwrap_command_args(
10681067
command.clone(),
1069-
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
1068+
&FileSystemSandboxPolicy::unrestricted(),
10701069
Path::new("/"),
10711070
Path::new("/"),
10721071
BwrapOptions {
@@ -1085,7 +1084,7 @@ mod tests {
10851084
let command = vec!["/bin/true".to_string()];
10861085
let args = create_bwrap_command_args(
10871086
command,
1088-
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
1087+
&FileSystemSandboxPolicy::unrestricted(),
10891088
Path::new("/"),
10901089
Path::new("/"),
10911090
BwrapOptions {
@@ -1399,22 +1398,18 @@ mod tests {
13991398
let missing_root = temp_dir.path().join("missing");
14001399
std::fs::create_dir(&existing_root).expect("create existing root");
14011400

1402-
let policy = SandboxPolicy::WorkspaceWrite {
1403-
writable_roots: vec![
1401+
let policy = FileSystemSandboxPolicy::workspace_write(
1402+
&[
14041403
AbsolutePathBuf::try_from(existing_root.as_path()).expect("absolute existing root"),
14051404
AbsolutePathBuf::try_from(missing_root.as_path()).expect("absolute missing root"),
14061405
],
1407-
network_access: false,
1408-
exclude_tmpdir_env_var: true,
1409-
exclude_slash_tmp: true,
1410-
};
1406+
/*exclude_tmpdir_env_var*/ true,
1407+
/*exclude_slash_tmp*/ true,
1408+
);
14111409

1412-
let args = create_filesystem_args(
1413-
&FileSystemSandboxPolicy::from(&policy),
1414-
temp_dir.path(),
1415-
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
1416-
)
1417-
.expect("filesystem args");
1410+
let args =
1411+
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
1412+
.expect("filesystem args");
14181413
let existing_root = path_to_string(&existing_root);
14191414
let missing_root = path_to_string(&missing_root);
14201415

@@ -1532,15 +1527,14 @@ mod tests {
15321527

15331528
#[test]
15341529
fn mounts_dev_before_writable_dev_binds() {
1535-
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
1536-
writable_roots: vec![AbsolutePathBuf::try_from(Path::new("/dev")).expect("/dev path")],
1537-
network_access: false,
1538-
exclude_tmpdir_env_var: true,
1539-
exclude_slash_tmp: true,
1540-
};
1530+
let sandbox_policy = FileSystemSandboxPolicy::workspace_write(
1531+
&[AbsolutePathBuf::try_from(Path::new("/dev")).expect("/dev path")],
1532+
/*exclude_tmpdir_env_var*/ true,
1533+
/*exclude_slash_tmp*/ true,
1534+
);
15411535

15421536
let args = create_filesystem_args(
1543-
&FileSystemSandboxPolicy::from(&sandbox_policy),
1537+
&sandbox_policy,
15441538
Path::new("/"),
15451539
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
15461540
)

codex-rs/linux-sandbox/src/landlock.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::path::Path;
88
use codex_protocol::error::CodexErr;
99
use codex_protocol::error::Result;
1010
use codex_protocol::error::SandboxErr;
11+
use codex_protocol::models::PermissionProfile;
1112
use codex_protocol::protocol::NetworkSandboxPolicy;
12-
use codex_protocol::protocol::SandboxPolicy;
1313
use codex_utils_absolute_path::AbsolutePathBuf;
1414

1515
use landlock::ABI;
@@ -39,14 +39,15 @@ use seccompiler::apply_filter;
3939
/// - installing the network seccomp filter when network access is disabled.
4040
///
4141
/// Filesystem restrictions are intentionally handled by bubblewrap.
42-
pub(crate) fn apply_sandbox_policy_to_current_thread(
43-
sandbox_policy: &SandboxPolicy,
44-
network_sandbox_policy: NetworkSandboxPolicy,
42+
pub(crate) fn apply_permission_profile_to_current_thread(
43+
permission_profile: &PermissionProfile,
4544
cwd: &Path,
4645
apply_landlock_fs: bool,
4746
allow_network_for_proxy: bool,
4847
proxy_routed_network: bool,
4948
) -> Result<()> {
49+
let (file_system_sandbox_policy, network_sandbox_policy) =
50+
permission_profile.to_runtime_permissions();
5051
let network_seccomp_mode = network_seccomp_mode(
5152
network_sandbox_policy,
5253
allow_network_for_proxy,
@@ -58,7 +59,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
5859
// we avoid this unless we need seccomp or we are explicitly using the
5960
// legacy Landlock filesystem pipeline.
6061
if network_seccomp_mode.is_some()
61-
|| (apply_landlock_fs && !sandbox_policy.has_full_disk_write_access())
62+
|| (apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access())
6263
{
6364
set_no_new_privs()?;
6465
}
@@ -67,15 +68,15 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
6768
install_network_seccomp_filter_on_current_thread(mode)?;
6869
}
6970

70-
if apply_landlock_fs && !sandbox_policy.has_full_disk_write_access() {
71-
if !sandbox_policy.has_full_disk_read_access() {
71+
if apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access() {
72+
if !file_system_sandbox_policy.has_full_disk_read_access() {
7273
return Err(CodexErr::UnsupportedOperation(
7374
"Restricted read-only access is not supported by the legacy Linux Landlock filesystem backend."
7475
.to_string(),
7576
));
7677
}
7778

78-
let writable_roots = sandbox_policy
79+
let writable_roots = file_system_sandbox_policy
7980
.get_writable_roots_with_cwd(cwd)
8081
.into_iter()
8182
.map(|writable_root| writable_root.root)

0 commit comments

Comments
 (0)