fix: preserve linux sandbox type contract

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-07-07 12:44:06 -07:00
co-authored by Codex noreply@openai.com
parent 27facbf916
commit dd16e95b6c
13 changed files with 57 additions and 126 deletions
+1 -2
View File
@@ -919,8 +919,7 @@ pub struct Config {
pub codex_self_exe: Option<PathBuf>,
/// Path to the `codex-linux-sandbox` executable. This must be set if
/// [`codex_sandboxing::SandboxType::LinuxBubblewrap`] or
/// [`codex_sandboxing::SandboxType::LinuxLegacyLandlock`] is used. Note that this
/// [`codex_sandboxing::SandboxType::LinuxSeccomp`] is used. Note that this
/// cannot be set in the config file: it must be set in code via
/// [`ConfigOverrides`].
///
+2 -5
View File
@@ -42,7 +42,6 @@ use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_sandboxing::WindowsSandboxFilesystemOverrides;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub(crate) use codex_sandboxing::is_likely_sandbox_denied;
#[cfg(test)]
use codex_sandboxing::permission_profile_supports_windows_restricted_token_sandbox;
@@ -349,15 +348,12 @@ pub fn build_exec_request(
let enforce_managed_network = network.is_some();
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
let mut sandbox_type = select_process_exec_tool_sandbox_type(
let sandbox_type = select_process_exec_tool_sandbox_type(
&file_system_sandbox_policy,
network_sandbox_policy,
windows_sandbox_level,
enforce_managed_network,
);
if use_legacy_landlock && sandbox_type == SandboxType::LinuxBubblewrap {
sandbox_type = SandboxType::LinuxLegacyLandlock;
}
tracing::debug!("Sandbox type: {sandbox_type:?}");
if let Some(network) = network.as_ref() {
@@ -399,6 +395,7 @@ pub fn build_exec_request(
network: network.as_ref(),
sandbox_policy_cwd: &sandbox_policy_cwd_uri,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock,
windows_sandbox_level,
windows_sandbox_private_desktop,
})
+5 -13
View File
@@ -30,7 +30,7 @@ fn make_exec_output(
fn sandbox_detection_requires_keywords() {
let output = make_exec_output(/*exit_code*/ 1, "", "", "");
assert!(!is_likely_sandbox_denied(
SandboxType::LinuxBubblewrap,
SandboxType::LinuxSeccomp,
&output
));
}
@@ -38,17 +38,14 @@ fn sandbox_detection_requires_keywords() {
#[test]
fn sandbox_detection_identifies_keyword_in_stderr() {
let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");
assert!(is_likely_sandbox_denied(
SandboxType::LinuxBubblewrap,
&output
));
assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));
}
#[test]
fn sandbox_detection_respects_quick_reject_exit_codes() {
let output = make_exec_output(/*exit_code*/ 127, "", "command not found", "");
assert!(!is_likely_sandbox_denied(
SandboxType::LinuxBubblewrap,
SandboxType::LinuxSeccomp,
&output
));
}
@@ -94,7 +91,7 @@ fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {
);
assert!(!is_likely_sandbox_denied(
SandboxType::LinuxBubblewrap,
SandboxType::LinuxSeccomp,
&output
));
}
@@ -1030,12 +1027,7 @@ fn build_exec_request_preserves_windows_workspace_roots() -> Result<()> {
fn sandbox_detection_flags_sigsys_exit_code() {
let exit_code = EXIT_CODE_SIGNAL_BASE + libc::SIGSYS;
let output = make_exec_output(exit_code, "", "", "");
for sandbox_type in [
SandboxType::LinuxBubblewrap,
SandboxType::LinuxLegacyLandlock,
] {
assert!(is_likely_sandbox_denied(sandbox_type, &output));
}
assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));
}
#[cfg(unix)]
-13
View File
@@ -260,13 +260,6 @@ impl ToolOrchestrator {
// Platform-specific flag gating is handled by SandboxManager::select_initial.
let use_legacy_landlock = turn_ctx.config.features.use_legacy_landlock();
let initial_sandbox =
if use_legacy_landlock && initial_sandbox == SandboxType::LinuxBubblewrap {
SandboxType::LinuxLegacyLandlock
} else {
initial_sandbox
};
#[allow(deprecated)]
let sandbox_policy_cwd = tool
.sandbox_cwd(req)
@@ -451,12 +444,6 @@ impl ToolOrchestrator {
} else {
SandboxType::None
};
let retry_sandbox =
if use_legacy_landlock && retry_sandbox == SandboxType::LinuxBubblewrap {
SandboxType::LinuxLegacyLandlock
} else {
retry_sandbox
};
let retry_codex_linux_sandbox_exe = if unsandboxed_allowed {
None
} else {
@@ -999,16 +999,13 @@ impl CoreShellCommandExecutor {
.split_first()
.ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?;
let sandbox_manager = SandboxManager::new();
let mut sandbox = sandbox_manager.select_initial(
let sandbox = sandbox_manager.select_initial(
&file_system_sandbox_policy,
network_sandbox_policy,
SandboxablePreference::Auto,
self.windows_sandbox_level,
self.network.is_some(),
);
if self.use_legacy_landlock && sandbox == SandboxType::LinuxBubblewrap {
sandbox = SandboxType::LinuxLegacyLandlock;
}
let cwd = PathUri::from_abs_path(workdir);
let sandbox_policy_cwd = PathUri::from_abs_path(&self.sandbox_policy_cwd);
let command = SandboxCommand {
@@ -1032,6 +1029,7 @@ impl CoreShellCommandExecutor {
network: self.network.as_ref(),
sandbox_policy_cwd: &sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: false,
})?;
+1
View File
@@ -459,6 +459,7 @@ impl<'a> SandboxAttempt<'a> {
codex_linux_sandbox_exe: self
.codex_linux_sandbox_exe
.map(std::path::PathBuf::as_path),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
+2 -5
View File
@@ -13,7 +13,6 @@ use codex_sandboxing::SandboxDirectSpawnTransformRequest;
use codex_sandboxing::SandboxExecRequest;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
@@ -104,16 +103,13 @@ impl FileSystemSandboxRunner {
let helper = &self.runtime_paths.codex_self_exe;
let sandbox_manager = SandboxManager::new();
let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
let mut sandbox = sandbox_manager.select_initial(
let sandbox = sandbox_manager.select_initial(
&file_system_policy,
network_policy,
SandboxablePreference::Auto,
sandbox_context.windows_sandbox_level,
/*has_managed_network_requirements*/ false,
);
if sandbox_context.use_legacy_landlock && sandbox == SandboxType::LinuxBubblewrap {
sandbox = SandboxType::LinuxLegacyLandlock;
}
let command = SandboxCommand {
program: helper.as_path().as_os_str().to_owned(),
args: vec![CODEX_FS_HELPER_ARG1.to_string()],
@@ -146,6 +142,7 @@ impl FileSystemSandboxRunner {
network: None,
sandbox_policy_cwd: &cwd.uri,
codex_linux_sandbox_exe: self.runtime_paths.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: sandbox_context.use_legacy_landlock,
windows_sandbox_level: sandbox_context.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_context
.windows_sandbox_private_desktop,
+3 -7
View File
@@ -78,16 +78,13 @@ pub(crate) fn prepare_exec_request(
);
let (file_system_policy, network_policy) = permissions.to_runtime_permissions();
let sandbox_manager = SandboxManager::new();
let mut sandbox = sandbox_manager.select_initial(
let sandbox = sandbox_manager.select_initial(
&file_system_policy,
network_policy,
SandboxablePreference::Require,
sandbox_context.windows_sandbox_level,
params.enforce_managed_network,
);
if sandbox_context.use_legacy_landlock && sandbox == SandboxType::LinuxBubblewrap {
sandbox = SandboxType::LinuxLegacyLandlock;
}
match sandbox {
SandboxType::None => {
return Err(invalid_params(
@@ -101,9 +98,7 @@ pub(crate) fn prepare_exec_request(
"sandboxed remote process launch is not supported on Windows".to_string(),
));
}
SandboxType::MacosSeatbelt
| SandboxType::LinuxBubblewrap
| SandboxType::LinuxLegacyLandlock => {}
SandboxType::MacosSeatbelt | SandboxType::LinuxSeccomp => {}
}
let (program, args) = params
.argv
@@ -132,6 +127,7 @@ pub(crate) fn prepare_exec_request(
network: None,
sandbox_policy_cwd,
codex_linux_sandbox_exe: runtime_paths.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: sandbox_context.use_legacy_landlock,
windows_sandbox_level: sandbox_context.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
},
+2 -4
View File
@@ -55,10 +55,8 @@ pub fn is_likely_sandbox_denied(
{
const EXIT_CODE_SIGNAL_BASE: i32 = 128;
const SIGSYS_CODE: i32 = libc::SIGSYS;
if matches!(
sandbox_type,
SandboxType::LinuxBubblewrap | SandboxType::LinuxLegacyLandlock
) && exec_output.exit_code == EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE
if sandbox_type == SandboxType::LinuxSeccomp
&& exec_output.exit_code == EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE
{
return true;
}
+10 -11
View File
@@ -35,19 +35,17 @@ const WINDOWS_SANDBOX_WRAPPER_SETUP_ENV_ALLOWLIST: &[&str] = &["USERNAME", "USER
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxBubblewrap,
LinuxLegacyLandlock,
LinuxSeccomp,
WindowsRestrictedToken,
}
impl SandboxType {
pub const fn as_metric_tag(self) -> &'static str {
pub fn as_metric_tag(self) -> &'static str {
match self {
Self::None => "none",
Self::MacosSeatbelt => "seatbelt",
Self::LinuxBubblewrap => "bubblewrap",
Self::LinuxLegacyLandlock => "legacy_landlock",
Self::WindowsRestrictedToken => "windows_sandbox",
SandboxType::None => "none",
SandboxType::MacosSeatbelt => "seatbelt",
SandboxType::LinuxSeccomp => "seccomp",
SandboxType::WindowsRestrictedToken => "windows_sandbox",
}
}
}
@@ -63,7 +61,7 @@ pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType
if cfg!(target_os = "macos") {
Some(SandboxType::MacosSeatbelt)
} else if cfg!(target_os = "linux") {
Some(SandboxType::LinuxBubblewrap)
Some(SandboxType::LinuxSeccomp)
} else if cfg!(target_os = "windows") {
if windows_sandbox_enabled {
Some(SandboxType::WindowsRestrictedToken)
@@ -141,6 +139,7 @@ pub struct SandboxTransformRequest<'a> {
pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a PathUri,
pub codex_linux_sandbox_exe: Option<&'a Path>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
}
@@ -332,10 +331,10 @@ impl SandboxManager {
network,
sandbox_policy_cwd,
codex_linux_sandbox_exe,
use_legacy_landlock,
windows_sandbox_level,
windows_sandbox_private_desktop,
} = request;
let use_legacy_landlock = sandbox == SandboxType::LinuxLegacyLandlock;
#[cfg(target_os = "macos")]
let managed_network = command.managed_network.as_ref();
let additional_permissions = command.additional_permissions.take();
@@ -383,7 +382,7 @@ impl SandboxManager {
}
#[cfg(not(target_os = "macos"))]
SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable),
SandboxType::LinuxBubblewrap | SandboxType::LinuxLegacyLandlock => {
SandboxType::LinuxSeccomp => {
let pending = pending_sandboxed_request?;
let exe = codex_linux_sandbox_exe
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
+11 -25
View File
@@ -102,6 +102,7 @@ fn unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_poli
network: None,
sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
})
@@ -157,6 +158,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
network: None,
sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
})
@@ -227,6 +229,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
network: None,
sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
})
@@ -300,9 +303,8 @@ fn managed_mitm_ca_bundle_becomes_readable_for_restricted_sandbox() {
}
#[cfg(target_os = "linux")]
fn transform_linux_request(
fn transform_linux_seccomp_request(
codex_linux_sandbox_exe: &std::path::Path,
sandbox: SandboxType,
) -> super::SandboxExecRequest {
let manager = SandboxManager::new();
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
@@ -319,12 +321,13 @@ fn transform_linux_request(
additional_permissions: None,
},
permissions: &permissions,
sandbox,
sandbox: SandboxType::LinuxSeccomp,
enforce_managed_network: false,
environment_id: None,
network: None,
sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: Some(codex_linux_sandbox_exe),
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
})
@@ -402,10 +405,9 @@ fn wsl1_allows_non_bubblewrap_linux_paths() {
#[cfg(target_os = "linux")]
#[test]
fn transform_linux_bubblewrap_preserves_helper_path_in_arg0_when_available() {
fn transform_linux_seccomp_preserves_helper_path_in_arg0_when_available() {
let codex_linux_sandbox_exe = std::path::PathBuf::from("/tmp/codex-linux-sandbox");
let exec_request =
transform_linux_request(&codex_linux_sandbox_exe, SandboxType::LinuxBubblewrap);
let exec_request = transform_linux_seccomp_request(&codex_linux_sandbox_exe);
assert_eq!(
exec_request.arg0,
@@ -415,10 +417,9 @@ fn transform_linux_bubblewrap_preserves_helper_path_in_arg0_when_available() {
#[cfg(target_os = "linux")]
#[test]
fn transform_linux_bubblewrap_uses_helper_alias_when_launcher_is_not_helper_path() {
fn transform_linux_seccomp_uses_helper_alias_when_launcher_is_not_helper_path() {
let codex_linux_sandbox_exe = std::path::PathBuf::from("/tmp/codex");
let exec_request =
transform_linux_request(&codex_linux_sandbox_exe, SandboxType::LinuxBubblewrap);
let exec_request = transform_linux_seccomp_request(&codex_linux_sandbox_exe);
assert_eq!(exec_request.arg0, Some("codex-linux-sandbox".to_string()));
}
@@ -519,6 +520,7 @@ fn transform_for_direct_spawn_windows_materializes_inner_helper() {
network: None,
sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false,
},
@@ -581,19 +583,3 @@ fn transform_for_direct_spawn_windows_materializes_inner_helper() {
);
assert!(materialized_helper.exists());
}
#[cfg(target_os = "linux")]
#[test]
fn transform_linux_landlock_uses_legacy_backend() {
let exec_request = transform_linux_request(
std::path::Path::new("/tmp/codex-linux-sandbox"),
SandboxType::LinuxLegacyLandlock,
);
assert_eq!(exec_request.sandbox, SandboxType::LinuxLegacyLandlock);
assert!(
exec_request
.command
.contains(&"--use-legacy-landlock".to_string())
);
}
+3 -8
View File
@@ -47,7 +47,6 @@ pub enum SandboxViolationEvent {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SandboxViolationBackend {
Bubblewrap,
LegacyLandlock,
ManagedNetworkProxy,
Seatbelt,
WindowsSandbox,
@@ -57,7 +56,6 @@ impl SandboxViolationBackend {
pub const fn as_str(self) -> &'static str {
match self {
Self::Bubblewrap => "bubblewrap",
Self::LegacyLandlock => "legacy_landlock",
Self::ManagedNetworkProxy => "managed_network_proxy",
Self::Seatbelt => "seatbelt",
Self::WindowsSandbox => "windows_sandbox",
@@ -143,8 +141,7 @@ fn classify_filesystem_sandbox_violation(
let backend = match sandbox_type {
SandboxType::None => return None,
SandboxType::MacosSeatbelt => SandboxViolationBackend::Seatbelt,
SandboxType::LinuxBubblewrap => SandboxViolationBackend::Bubblewrap,
SandboxType::LinuxLegacyLandlock => SandboxViolationBackend::LegacyLandlock,
SandboxType::LinuxSeccomp => SandboxViolationBackend::Bubblewrap,
SandboxType::WindowsRestrictedToken => SandboxViolationBackend::WindowsSandbox,
};
@@ -163,10 +160,8 @@ fn classify_filesystem_sandbox_violation(
#[cfg(unix)]
{
if matches!(
sandbox_type,
SandboxType::LinuxBubblewrap | SandboxType::LinuxLegacyLandlock
) && exec_output.exit_code == EXIT_CODE_SIGNAL_BASE + libc::SIGSYS
if sandbox_type == SandboxType::LinuxSeccomp
&& exec_output.exit_code == EXIT_CODE_SIGNAL_BASE + libc::SIGSYS
{
return Some(FileSystemSandboxViolation {
backend,
+15 -29
View File
@@ -37,7 +37,7 @@ fn classifies_legacy_denial_keywords() {
let output = make_exec_output(/*exit_code*/ 1, "", keyword, "");
assert!(
classify_filesystem_sandbox_violation(SandboxType::LinuxBubblewrap, &output).is_some(),
classify_filesystem_sandbox_violation(SandboxType::LinuxSeccomp, &output).is_some(),
"{keyword}"
);
}
@@ -49,7 +49,7 @@ fn normalizes_backend_keywords_as_policy_denied() {
let output = make_exec_output(/*exit_code*/ 1, "", keyword, "");
assert_eq!(
classify_filesystem_sandbox_violation(SandboxType::LinuxBubblewrap, &output),
classify_filesystem_sandbox_violation(SandboxType::LinuxSeccomp, &output),
Some(FileSystemSandboxViolation {
backend: SandboxViolationBackend::Bubblewrap,
reason: FileSystemSandboxViolationReason::PolicyDenied,
@@ -73,24 +73,21 @@ fn preserves_legacy_denial_ordering() {
assert!(
classify_filesystem_sandbox_violation(
SandboxType::LinuxBubblewrap,
SandboxType::LinuxSeccomp,
&quick_reject_without_keyword
)
.is_none()
);
assert!(
classify_filesystem_sandbox_violation(
SandboxType::LinuxBubblewrap,
SandboxType::LinuxSeccomp,
&quick_reject_with_keyword
)
.is_some()
);
assert!(
classify_filesystem_sandbox_violation(
SandboxType::LinuxBubblewrap,
&zero_exit_with_keyword
)
.is_none()
classify_filesystem_sandbox_violation(SandboxType::LinuxSeccomp, &zero_exit_with_keyword)
.is_none()
);
assert!(
classify_filesystem_sandbox_violation(SandboxType::None, &non_sandbox_with_keyword)
@@ -169,26 +166,15 @@ fn classifies_linux_sigsys_exit() {
"",
);
for (sandbox_type, backend) in [
(
SandboxType::LinuxBubblewrap,
SandboxViolationBackend::Bubblewrap,
),
(
SandboxType::LinuxLegacyLandlock,
SandboxViolationBackend::LegacyLandlock,
),
] {
assert_eq!(
classify_filesystem_sandbox_violation(sandbox_type, &output),
Some(FileSystemSandboxViolation {
backend,
reason: FileSystemSandboxViolationReason::SignalSyscall,
path: None,
output_snippet: String::new(),
})
);
}
assert_eq!(
classify_filesystem_sandbox_violation(SandboxType::LinuxSeccomp, &output),
Some(FileSystemSandboxViolation {
backend: SandboxViolationBackend::Bubblewrap,
reason: FileSystemSandboxViolationReason::SignalSyscall,
path: None,
output_snippet: String::new(),
})
);
}
#[test]