use crate::policy_transforms::should_require_platform_sandbox; use codex_protocol::models::PermissionProfile; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Output; const SYSTEM_BWRAP_PROGRAM: &str = "bwrap"; const MISSING_BWRAP_WARNING: &str = concat!( "Codex could not find bubblewrap on PATH. ", "Install bubblewrap with your OS package manager. ", "See the sandbox prerequisites: ", "https://developers.openai.com/codex/concepts/sandboxing#prerequisites. ", "Codex will use the vendored bubblewrap in the meantime.", ); const USER_NAMESPACE_WARNING: &str = "Codex's Linux sandbox uses bubblewrap and needs access to create user namespaces."; pub(crate) const WSL1_BWRAP_WARNING: &str = concat!( "Codex's Linux sandbox uses bubblewrap, which is not supported on WSL1 ", "because WSL1 cannot create the required user namespaces. ", "Use WSL2 for sandboxed shell commands." ); const USER_NAMESPACE_FAILURES: [&str; 4] = [ "loopback: Failed RTM_NEWADDR", "loopback: Failed RTM_NEWLINK", "setting up uid map: Permission denied", "No permissions to create a new namespace", ]; pub fn system_bwrap_warning(permission_profile: &PermissionProfile) -> Option { if !should_warn_about_system_bwrap(permission_profile) { return None; } let system_bwrap_path = find_system_bwrap_in_path(); system_bwrap_warning_for_path(system_bwrap_path.as_deref()) } fn should_warn_about_system_bwrap(permission_profile: &PermissionProfile) -> bool { let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions(); should_require_platform_sandbox( &file_system_policy, network_policy, /*has_managed_network_requirements*/ false, ) } fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option { if is_wsl1() { return Some(WSL1_BWRAP_WARNING.to_string()); } let Some(system_bwrap_path) = system_bwrap_path else { return Some(MISSING_BWRAP_WARNING.to_string()); }; if !system_bwrap_has_user_namespace_access(system_bwrap_path) { return Some(USER_NAMESPACE_WARNING.to_string()); } None } fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path) -> bool { let output = match Command::new(system_bwrap_path) .args([ "--unshare-user", "--unshare-net", "--ro-bind", "/", "/", "/bin/true", ]) .output() { Ok(output) => output, Err(_) => return true, }; output.status.success() || !is_user_namespace_failure(&output) } pub(crate) fn is_wsl1() -> bool { std::fs::read_to_string("/proc/version") .is_ok_and(|proc_version| proc_version_indicates_wsl1(&proc_version)) } fn proc_version_indicates_wsl1(proc_version: &str) -> bool { let proc_version = proc_version.to_ascii_lowercase(); let mut remaining = proc_version.as_str(); while let Some(marker) = remaining.find("wsl") { let version_start = marker + "wsl".len(); let version_digits: String = remaining[version_start..] .chars() .take_while(char::is_ascii_digit) .collect(); if let Ok(version) = version_digits.parse::() { return version == 1; } remaining = &remaining[version_start..]; } proc_version.contains("microsoft") && !proc_version.contains("microsoft-standard") } fn is_user_namespace_failure(output: &Output) -> bool { let stderr = String::from_utf8_lossy(&output.stderr); USER_NAMESPACE_FAILURES .iter() .any(|failure| stderr.contains(failure)) } pub fn find_system_bwrap_in_path() -> Option { let search_path = std::env::var_os("PATH")?; let cwd = std::env::current_dir().ok()?; find_system_bwrap_in_search_paths(std::env::split_paths(&search_path), &cwd) } fn find_system_bwrap_in_search_paths( search_paths: impl IntoIterator, cwd: &Path, ) -> Option { let search_path = std::env::join_paths(search_paths).ok()?; let cwd = std::fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf()); which::which_in_all(SYSTEM_BWRAP_PROGRAM, Some(search_path), &cwd) .ok()? .find_map(|path| { let path = std::fs::canonicalize(path).ok()?; if path.starts_with(&cwd) { None } else { Some(path) } }) } #[cfg(test)] #[path = "bwrap_tests.rs"] mod tests;