refactor: make bubblewrap the default Linux sandbox (#13996)

## Summary
- make bubblewrap the default Linux sandbox and keep
`use_legacy_landlock` as the only override
- remove `use_linux_sandbox_bwrap` from feature, config, schema, and
docs surfaces
- update Linux sandbox selection, CLI/config plumbing, and related
tests/docs to match the new default
- fold in the follow-up CI fixes for request-permissions responses and
Linux read-only sandbox error text
This commit is contained in:
viyatb-oai 2026-03-11 23:31:18 -07:00 committed by GitHub
parent b5f927b973
commit 04892b4ceb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 184 additions and 222 deletions

View file

@ -1723,7 +1723,7 @@ impl CodexMessageProcessor {
let outgoing = self.outgoing.clone();
let request_for_task = request.clone();
let started_network_proxy_for_task = started_network_proxy;
let use_linux_sandbox_bwrap = self.config.features.enabled(Feature::UseLinuxSandboxBwrap);
let use_legacy_landlock = self.config.features.use_legacy_landlock();
let size = match size.map(crate::command_exec::terminal_size_from_protocol) {
Some(Ok(size)) => Some(size),
Some(Err(error)) => {
@ -1740,7 +1740,7 @@ impl CodexMessageProcessor {
effective_network_sandbox_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
use_legacy_landlock,
) {
Ok(exec_request) => {
if let Err(error) = self

View file

@ -710,6 +710,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let marker = format!(
"codex-command-exec-marker-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_nanos()
);
let (mut process, bind_addr) = spawn_websocket_server(codex_home.path()).await?;
@ -726,7 +732,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
"command/exec",
101,
Some(serde_json::json!({
"command": ["sh", "-lc", "printf 'ready\\n%s\\n' $$; sleep 30"],
"command": [
"python3",
"-c",
"import time; print('ready', flush=True); time.sleep(30)",
marker,
],
"processId": "shared-process",
"streamStdoutStderr": true,
})),
@ -737,12 +748,8 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
assert_eq!(delta.process_id, "shared-process");
assert_eq!(delta.stream, CommandExecOutputStream::Stdout);
let delta_text = String::from_utf8(STANDARD.decode(&delta.delta_base64)?)?;
let pid = delta_text
.lines()
.last()
.context("delta should include shell pid")?
.parse::<u32>()
.context("parse shell pid")?;
assert!(delta_text.contains("ready"));
wait_for_process_marker(&marker, true).await?;
send_request(
&mut ws2,
@ -766,12 +773,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
terminate_error.error.message,
"no active command/exec for process id \"shared-process\""
);
assert!(process_is_alive(pid)?);
wait_for_process_marker(&marker, true).await?;
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
ws1.close(None).await?;
wait_for_process_exit(pid).await?;
wait_for_process_marker(&marker, false).await?;
process
.kill()
@ -855,24 +862,25 @@ async fn read_initialize_response(
}
}
async fn wait_for_process_exit(pid: u32) -> Result<()> {
async fn wait_for_process_marker(marker: &str, should_exist: bool) -> Result<()> {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if !process_is_alive(pid)? {
if process_with_marker_exists(marker)? == should_exist {
return Ok(());
}
if Instant::now() >= deadline {
anyhow::bail!("process {pid} was still alive after websocket disconnect");
let expectation = if should_exist { "appear" } else { "exit" };
anyhow::bail!("process marker {marker:?} did not {expectation} before timeout");
}
sleep(Duration::from_millis(50)).await;
}
}
fn process_is_alive(pid: u32) -> Result<bool> {
let status = std::process::Command::new("kill")
.arg("-0")
.arg(pid.to_string())
.status()
.context("spawn kill -0")?;
Ok(status.success())
fn process_with_marker_exists(marker: &str) -> Result<bool> {
let output = std::process::Command::new("ps")
.args(["-axo", "command"])
.output()
.context("spawn ps -axo command")?;
let stdout = String::from_utf8(output.stdout).context("decode ps output")?;
Ok(stdout.lines().any(|line| line.contains(marker)))
}

View file

@ -250,19 +250,18 @@ async fn run_command_under_sandbox(
.await?
}
SandboxType::Landlock => {
use codex_core::features::Feature;
#[expect(clippy::expect_used)]
let codex_linux_sandbox_exe = config
.codex_linux_sandbox_exe
.expect("codex-linux-sandbox executable not found");
let use_bwrap_sandbox = config.features.enabled(Feature::UseLinuxSandboxBwrap);
let use_legacy_landlock = config.features.use_legacy_landlock();
spawn_command_under_linux_sandbox(
codex_linux_sandbox_exe,
command,
cwd,
config.permissions.sandbox_policy.get(),
sandbox_policy_cwd.as_path(),
use_bwrap_sandbox,
use_legacy_landlock,
stdio_policy,
network.as_ref(),
env,

View file

@ -32,7 +32,7 @@ pub struct LandlockCommand {
#[clap(skip)]
pub config_overrides: CliConfigOverrides,
/// Full command args to run under landlock.
/// Full command args to run under the Linux sandbox.
#[arg(trailing_var_arg = true)]
pub command: Vec<String>,
}

View file

@ -239,7 +239,7 @@ enum SandboxCommand {
#[clap(visible_alias = "seatbelt")]
Macos(SeatbeltCommand),
/// Run a command under Landlock+seccomp (Linux only).
/// Run a command under the Linux sandbox (bubblewrap by default).
#[clap(visible_alias = "landlock")]
Linux(LandlockCommand),

View file

@ -480,7 +480,7 @@
"unified_exec": {
"type": "boolean"
},
"use_linux_sandbox_bwrap": {
"use_legacy_landlock": {
"type": "boolean"
},
"voice_transcription": {
@ -1985,7 +1985,7 @@
"unified_exec": {
"type": "boolean"
},
"use_linux_sandbox_bwrap": {
"use_legacy_landlock": {
"type": "boolean"
},
"voice_transcription": {

View file

@ -1290,9 +1290,7 @@ impl Session {
cwd.clone(),
session_configuration.sandbox_policy.get(),
session_configuration.windows_sandbox_level,
per_turn_config
.features
.enabled(Feature::UseLinuxSandboxBwrap),
per_turn_config.features.use_legacy_landlock(),
));
let (current_date, timezone) = local_time_context();
TurnContext {
@ -1802,7 +1800,7 @@ impl Session {
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: session_configuration.cwd.clone(),
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: config.features.use_legacy_landlock(),
};
let mut required_mcp_servers: Vec<String> = mcp_servers
.iter()
@ -2275,9 +2273,7 @@ impl Session {
sandbox_policy: per_turn_config.permissions.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: per_turn_config.cwd.clone(),
use_linux_sandbox_bwrap: per_turn_config
.features
.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: per_turn_config.features.use_legacy_landlock(),
};
if let Err(e) = self
.services
@ -3938,7 +3934,7 @@ impl Session {
sandbox_policy: turn_context.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
sandbox_cwd: turn_context.cwd.clone(),
use_linux_sandbox_bwrap: turn_context.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
};
{
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
@ -5215,9 +5211,7 @@ async fn spawn_review_thread(
parent_turn_context.cwd.clone(),
parent_turn_context.sandbox_policy.get(),
parent_turn_context.windows_sandbox_level,
parent_turn_context
.features
.enabled(Feature::UseLinuxSandboxBwrap),
parent_turn_context.features.use_legacy_landlock(),
));
let review_turn_context = TurnContext {

View file

@ -31,7 +31,6 @@ use crate::config::types::AppsConfigToml;
use crate::default_client::create_client;
use crate::default_client::is_first_party_chat_originator;
use crate::default_client::originator;
use crate::features::Feature;
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
use crate::mcp::McpManager;
use crate::mcp::ToolPluginProvenance;
@ -203,7 +202,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
sandbox_policy: SandboxPolicy::new_read_only_policy(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: config.features.use_legacy_landlock(),
};
let (mcp_connection_manager, cancel_token) = McpConnectionManager::new(

View file

@ -185,7 +185,7 @@ pub async fn process_exec_tool_call(
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_linux_sandbox_bwrap: bool,
use_legacy_landlock: bool,
stdout_stream: Option<StdoutStream>,
) -> Result<ExecToolCallOutput> {
let exec_req = build_exec_request(
@ -195,7 +195,7 @@ pub async fn process_exec_tool_call(
network_sandbox_policy,
sandbox_cwd,
codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
use_legacy_landlock,
)?;
// Route through the sandboxing module for a single, unified execution path.
@ -211,7 +211,7 @@ pub fn build_exec_request(
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_linux_sandbox_bwrap: bool,
use_legacy_landlock: bool,
) -> Result<ExecRequest> {
let windows_sandbox_level = params.windows_sandbox_level;
let enforce_managed_network = params.network.is_some();
@ -269,7 +269,7 @@ pub fn build_exec_request(
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(),
use_linux_sandbox_bwrap,
use_legacy_landlock,
windows_sandbox_level,
})
.map_err(CodexErr::from)?;

View file

@ -108,8 +108,9 @@ pub enum Feature {
WebSearchCached,
/// Legacy search-tool feature flag kept for backward compatibility.
SearchTool,
/// Use the bubblewrap-based Linux sandbox pipeline.
UseLinuxSandboxBwrap,
/// Use the legacy Landlock Linux sandbox fallback instead of the default
/// bubblewrap pipeline.
UseLegacyLandlock,
/// Allow the model to request approval and propose exec rules.
RequestRule,
/// Enable Windows sandbox (restricted token) on Windows.
@ -284,6 +285,10 @@ impl Features {
self.enabled(Feature::Apps) && auth.is_some_and(CodexAuth::is_chatgpt_auth)
}
pub fn use_legacy_landlock(&self) -> bool {
self.enabled(Feature::UseLegacyLandlock)
}
pub fn enable(&mut self, f: Feature) -> &mut Self {
self.enabled.insert(f);
self
@ -636,16 +641,9 @@ pub const FEATURES: &[FeatureSpec] = &[
default_enabled: false,
},
FeatureSpec {
id: Feature::UseLinuxSandboxBwrap,
key: "use_linux_sandbox_bwrap",
#[cfg(target_os = "linux")]
stage: Stage::Experimental {
name: "Bubblewrap sandbox",
menu_description: "Try the new linux sandbox based on bubblewrap.",
announcement: "NEW: Linux bubblewrap sandbox offers stronger filesystem and network controls than Landlock alone, including keeping .git and .codex read-only inside writable workspaces. Enable it in /experimental and restart Codex to try it.",
},
#[cfg(not(target_os = "linux"))]
stage: Stage::UnderDevelopment,
id: Feature::UseLegacyLandlock,
key: "use_legacy_landlock",
stage: Stage::Stable,
default_enabled: false,
},
FeatureSpec {
@ -932,24 +930,10 @@ mod tests {
}
}
#[cfg(target_os = "linux")]
#[test]
fn use_linux_sandbox_bwrap_is_experimental_on_linux() {
assert!(matches!(
Feature::UseLinuxSandboxBwrap.stage(),
Stage::Experimental { .. }
));
assert_eq!(Feature::UseLinuxSandboxBwrap.default_enabled(), false);
}
#[cfg(not(target_os = "linux"))]
#[test]
fn use_linux_sandbox_bwrap_is_under_development_off_linux() {
assert_eq!(
Feature::UseLinuxSandboxBwrap.stage(),
Stage::UnderDevelopment
);
assert_eq!(Feature::UseLinuxSandboxBwrap.default_enabled(), false);
fn use_legacy_landlock_is_stable_and_disabled_by_default() {
assert_eq!(Feature::UseLegacyLandlock.stage(), Stage::Stable);
assert_eq!(Feature::UseLegacyLandlock.default_enabled(), false);
}
#[test]

View file

@ -11,7 +11,7 @@ use std::path::PathBuf;
use tokio::process::Child;
/// Spawn a shell tool command under the Linux sandbox helper
/// (codex-linux-sandbox), which currently uses bubblewrap for filesystem
/// (codex-linux-sandbox), which defaults to bubblewrap for filesystem
/// isolation plus seccomp for network restrictions.
///
/// Unlike macOS Seatbelt where we directly embed the policy text, the Linux
@ -25,7 +25,7 @@ pub async fn spawn_command_under_linux_sandbox<P>(
command_cwd: PathBuf,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
use_legacy_landlock: bool,
stdio_policy: StdioPolicy,
network: Option<&NetworkProxy>,
env: HashMap<String, String>,
@ -42,7 +42,7 @@ where
&file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd,
use_bwrap_sandbox,
use_legacy_landlock,
allow_network_for_proxy(false),
);
let arg0 = Some("codex-linux-sandbox");
@ -69,7 +69,7 @@ pub(crate) fn allow_network_for_proxy(enforce_managed_network: bool) -> bool {
/// Converts the sandbox policies into the CLI invocation for
/// `codex-linux-sandbox`.
///
/// The helper performs the actual sandboxing (bubblewrap + seccomp) after
/// The helper performs the actual sandboxing (bubblewrap by default + seccomp) after
/// parsing these arguments. Policy JSON flags are emitted before helper feature
/// flags so the argv order matches the helper's CLI shape. See
/// `docs/linux_sandbox.md` for the Linux semantics.
@ -80,7 +80,7 @@ pub(crate) fn create_linux_sandbox_command_args_for_policies(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
use_legacy_landlock: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
let sandbox_policy_json = serde_json::to_string(sandbox_policy)
@ -104,8 +104,8 @@ pub(crate) fn create_linux_sandbox_command_args_for_policies(
"--network-sandbox-policy".to_string(),
network_policy_json,
];
if use_bwrap_sandbox {
linux_cmd.push("--use-bwrap-sandbox".to_string());
if use_legacy_landlock {
linux_cmd.push("--use-legacy-landlock".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
@ -121,7 +121,7 @@ pub(crate) fn create_linux_sandbox_command_args_for_policies(
pub(crate) fn create_linux_sandbox_command_args(
command: Vec<String>,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
use_legacy_landlock: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
let sandbox_policy_cwd = sandbox_policy_cwd
@ -130,8 +130,8 @@ pub(crate) fn create_linux_sandbox_command_args(
.to_string();
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());
if use_legacy_landlock {
linux_cmd.push("--use-legacy-landlock".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
@ -153,20 +153,20 @@ mod tests {
use pretty_assertions::assert_eq;
#[test]
fn bwrap_flags_are_feature_gated() {
fn legacy_landlock_flag_is_included_when_requested() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let with_bwrap = create_linux_sandbox_command_args(command.clone(), cwd, true, false);
let default_bwrap = create_linux_sandbox_command_args(command.clone(), cwd, false, false);
assert_eq!(
with_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
true
default_bwrap.contains(&"--use-legacy-landlock".to_string()),
false
);
let without_bwrap = create_linux_sandbox_command_args(command, cwd, false, false);
let legacy_landlock = create_linux_sandbox_command_args(command, cwd, true, false);
assert_eq!(
without_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
false
legacy_landlock.contains(&"--use-legacy-landlock".to_string()),
true
);
}

View file

@ -304,7 +304,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
sandbox_policy: SandboxPolicy::new_read_only_policy(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: config.features.use_legacy_landlock(),
};
let (mcp_connection_manager, cancel_token) = McpConnectionManager::new(

View file

@ -591,7 +591,7 @@ pub struct SandboxState {
pub codex_linux_sandbox_exe: Option<PathBuf>,
pub sandbox_cwd: PathBuf,
#[serde(default)]
pub use_linux_sandbox_bwrap: bool,
pub use_legacy_landlock: bool,
}
/// A thin wrapper around a set of running [`RmcpClient`] instances.

View file

@ -6,7 +6,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
pub(crate) fn sandbox_tag(
policy: &SandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel,
use_linux_sandbox_bwrap: bool,
use_legacy_landlock: bool,
) -> &'static str {
if matches!(policy, SandboxPolicy::DangerFullAccess) {
return "none";
@ -18,7 +18,7 @@ pub(crate) fn sandbox_tag(
{
return "windows_elevated";
}
if cfg!(target_os = "linux") && use_linux_sandbox_bwrap {
if cfg!(target_os = "linux") && !use_legacy_landlock {
return "linux_bubblewrap";
}
@ -38,33 +38,33 @@ mod tests {
use pretty_assertions::assert_eq;
#[test]
fn danger_full_access_is_untagged_even_when_bubblewrap_is_enabled() {
fn danger_full_access_is_untagged_even_when_bubblewrap_is_default() {
let actual = sandbox_tag(
&SandboxPolicy::DangerFullAccess,
WindowsSandboxLevel::Disabled,
true,
false,
);
assert_eq!(actual, "none");
}
#[test]
fn external_sandbox_keeps_external_tag_when_bubblewrap_is_enabled() {
fn external_sandbox_keeps_external_tag_when_bubblewrap_is_default() {
let actual = sandbox_tag(
&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Enabled,
},
WindowsSandboxLevel::Disabled,
true,
false,
);
assert_eq!(actual, "external");
}
#[test]
fn bubblewrap_feature_sets_distinct_linux_tag() {
fn bubblewrap_default_sets_distinct_linux_tag() {
let actual = sandbox_tag(
&SandboxPolicy::new_read_only_policy(),
WindowsSandboxLevel::Disabled,
true,
false,
);
let expected = if cfg!(target_os = "linux") {
"linux_bubblewrap"

View file

@ -94,7 +94,7 @@ pub(crate) struct SandboxTransformRequest<'a> {
#[cfg(target_os = "macos")]
pub macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>,
pub codex_linux_sandbox_exe: Option<&'a PathBuf>,
pub use_linux_sandbox_bwrap: bool,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
}
@ -571,7 +571,7 @@ impl SandboxManager {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions,
codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
use_legacy_landlock,
windows_sandbox_level,
} = request;
#[cfg(not(target_os = "macos"))]
@ -653,7 +653,7 @@ impl SandboxManager {
&effective_file_system_policy,
effective_network_policy,
sandbox_policy_cwd,
use_linux_sandbox_bwrap,
use_legacy_landlock,
allow_proxy_network,
);
let mut full_command = Vec::with_capacity(1 + args.len());
@ -886,7 +886,7 @@ mod tests {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
})
.expect("transform");
@ -1219,7 +1219,7 @@ mod tests {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
})
.expect("transform");
@ -1291,7 +1291,7 @@ mod tests {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
})
.expect("transform");

View file

@ -884,9 +884,7 @@ impl JsReplManager {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_ref(),
use_linux_sandbox_bwrap: turn
.features
.enabled(crate::features::Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: turn.features.use_legacy_landlock(),
windows_sandbox_level: turn.windows_sandbox_level,
})
.map_err(|err| format!("failed to configure sandbox for js_repl: {err}"))?;

View file

@ -9,7 +9,6 @@ caching).
use crate::error::CodexErr;
use crate::error::SandboxErr;
use crate::exec::ExecToolCallOutput;
use crate::features::Feature;
use crate::guardian::GUARDIAN_REJECTION_MESSAGE;
use crate::guardian::routes_approval_to_guardian;
use crate::network_policy_decision::network_approval_context_from_payload;
@ -186,7 +185,7 @@ impl ToolOrchestrator {
// Platform-specific flag gating is handled by SandboxManager::select_initial
// via crate::safety::get_platform_sandbox(..).
let use_linux_sandbox_bwrap = turn_ctx.features.enabled(Feature::UseLinuxSandboxBwrap);
let use_legacy_landlock = turn_ctx.features.use_legacy_landlock();
let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox,
policy: &turn_ctx.sandbox_policy,
@ -196,7 +195,7 @@ impl ToolOrchestrator {
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
use_linux_sandbox_bwrap,
use_legacy_landlock,
windows_sandbox_level: turn_ctx.windows_sandbox_level,
};
@ -318,7 +317,7 @@ impl ToolOrchestrator {
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap,
use_legacy_landlock,
windows_sandbox_level: turn_ctx.windows_sandbox_level,
};

View file

@ -4,7 +4,6 @@ use std::time::Duration;
use std::time::Instant;
use crate::client_common::tools::ToolSpec;
use crate::features::Feature;
use crate::function_tool::FunctionCallError;
use crate::memories::usage::emit_metric_for_tool_read;
use crate::protocol::SandboxPolicy;
@ -174,10 +173,7 @@ impl ToolRegistry {
sandbox_tag(
&invocation.turn.sandbox_policy,
invocation.turn.windows_sandbox_level,
invocation
.turn
.features
.enabled(Feature::UseLinuxSandboxBwrap),
invocation.turn.features.use_legacy_landlock(),
),
),
(
@ -505,7 +501,7 @@ async fn dispatch_after_tool_use_hook(
sandbox: sandbox_tag(
&turn.sandbox_policy,
turn.windows_sandbox_level,
turn.features.enabled(Feature::UseLinuxSandboxBwrap),
turn.features.use_legacy_landlock(),
)
.to_string(),
sandbox_policy: sandbox_policy_tag(&turn.sandbox_policy).to_string(),

View file

@ -146,7 +146,7 @@ pub(super) async fn try_run_zsh_fork(
.macos_seatbelt_profile_extensions
.clone(),
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_linux_sandbox_bwrap: ctx.turn.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
let main_execve_wrapper_exe = ctx
.session
@ -258,7 +258,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
.macos_seatbelt_profile_extensions
.clone(),
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_linux_sandbox_bwrap: ctx.turn.features.enabled(Feature::UseLinuxSandboxBwrap),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
let main_execve_wrapper_exe = ctx
.session
@ -855,7 +855,7 @@ struct CoreShellCommandExecutor {
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
macos_seatbelt_profile_extensions: Option<MacOsSeatbeltProfileExtensions>,
codex_linux_sandbox_exe: Option<PathBuf>,
use_linux_sandbox_bwrap: bool,
use_legacy_landlock: bool,
}
struct PrepareSandboxedExecParams<'a> {
@ -1052,7 +1052,7 @@ impl CoreShellCommandExecutor {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_ref(),
use_linux_sandbox_bwrap: self.use_linux_sandbox_bwrap,
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
})?;
if let Some(network) = exec_request.network.as_ref() {

View file

@ -611,7 +611,7 @@ async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions
..Default::default()
}),
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
};
let prepared = executor
@ -660,7 +660,7 @@ async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions()
sandbox_policy_cwd: cwd.to_path_buf(),
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
};
let permissions = Permissions {
@ -737,7 +737,7 @@ async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_mac
..Default::default()
}),
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
use_legacy_landlock: false,
};
let prepared = executor

View file

@ -330,7 +330,7 @@ pub(crate) struct SandboxAttempt<'a> {
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a Path,
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
pub use_linux_sandbox_bwrap: bool,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
}
@ -353,7 +353,7 @@ impl<'a> SandboxAttempt<'a> {
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe,
use_linux_sandbox_bwrap: self.use_linux_sandbox_bwrap,
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
})
}

View file

@ -132,16 +132,11 @@ impl TurnMetadataState {
cwd: PathBuf,
sandbox_policy: &SandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel,
use_linux_sandbox_bwrap: bool,
use_legacy_landlock: bool,
) -> Self {
let repo_root = get_git_repo_root(&cwd).map(|root| root.to_string_lossy().into_owned());
let sandbox = Some(
sandbox_tag(
sandbox_policy,
windows_sandbox_level,
use_linux_sandbox_bwrap,
)
.to_string(),
sandbox_tag(sandbox_policy, windows_sandbox_level, use_legacy_landlock).to_string(),
);
let base_metadata = build_turn_metadata_bag(Some(turn_id), sandbox, None, None);
let base_header = base_metadata
@ -300,19 +295,19 @@ mod tests {
}
#[test]
fn turn_metadata_state_respects_linux_bubblewrap_toggle() {
fn turn_metadata_state_respects_legacy_landlock_flag() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().to_path_buf();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let without_bubblewrap = TurnMetadataState::new(
let default_bubblewrap = TurnMetadataState::new(
"turn-a".to_string(),
cwd.clone(),
&sandbox_policy,
WindowsSandboxLevel::Disabled,
false,
);
let with_bubblewrap = TurnMetadataState::new(
let legacy_landlock = TurnMetadataState::new(
"turn-b".to_string(),
cwd,
&sandbox_policy,
@ -320,30 +315,33 @@ mod tests {
true,
);
let without_bubblewrap_header = without_bubblewrap
let default_bubblewrap_header = default_bubblewrap
.current_header_value()
.expect("without_bubblewrap_header");
let with_bubblewrap_header = with_bubblewrap
.expect("default_bubblewrap_header");
let legacy_landlock_header = legacy_landlock
.current_header_value()
.expect("with_bubblewrap_header");
.expect("legacy_landlock_header");
let without_bubblewrap_json: Value =
serde_json::from_str(&without_bubblewrap_header).expect("without_bubblewrap_json");
let with_bubblewrap_json: Value =
serde_json::from_str(&with_bubblewrap_header).expect("with_bubblewrap_json");
let default_bubblewrap_json: Value =
serde_json::from_str(&default_bubblewrap_header).expect("default_bubblewrap_json");
let legacy_landlock_json: Value =
serde_json::from_str(&legacy_landlock_header).expect("legacy_landlock_json");
let without_bubblewrap_sandbox = without_bubblewrap_json
let default_bubblewrap_sandbox = default_bubblewrap_json
.get("sandbox")
.and_then(Value::as_str);
let with_bubblewrap_sandbox = with_bubblewrap_json.get("sandbox").and_then(Value::as_str);
let legacy_landlock_sandbox = legacy_landlock_json.get("sandbox").and_then(Value::as_str);
let expected_with_bubblewrap =
sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled, true);
assert_eq!(with_bubblewrap_sandbox, Some(expected_with_bubblewrap));
let expected_default_bubblewrap =
sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled, false);
assert_eq!(
default_bubblewrap_sandbox,
Some(expected_default_bubblewrap)
);
if cfg!(target_os = "linux") {
assert_eq!(with_bubblewrap_sandbox, Some("linux_bubblewrap"));
assert_ne!(with_bubblewrap_sandbox, without_bubblewrap_sandbox);
assert_eq!(default_bubblewrap_sandbox, Some("linux_bubblewrap"));
assert_ne!(default_bubblewrap_sandbox, legacy_landlock_sandbox);
}
}
}

View file

@ -1321,7 +1321,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
expectation: Expectation::FileNotCreated {
target: TargetPath::Workspace("ro_never.txt"),
message_contains: if cfg!(target_os = "linux") {
&["Permission denied"]
&["Permission denied|Read-only file system"]
} else {
&[
"Permission denied|Operation not permitted|operation not permitted|\
@ -1468,7 +1468,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
expectation: Expectation::FileNotCreated {
target: TargetPath::OutsideWorkspace("ww_never.txt"),
message_contains: if cfg!(target_os = "linux") {
&["Permission denied"]
&["Permission denied|Read-only file system"]
} else {
&[
"Permission denied|Operation not permitted|operation not permitted|\
@ -2290,20 +2290,17 @@ allow_local_binding = true
test.config.permissions.network.is_some(),
"expected managed network proxy config to be present"
);
let runtime_proxy = test
.session_configured
test.session_configured
.network_proxy
.as_ref()
.expect("expected runtime managed network proxy addresses");
let proxy_addr = runtime_proxy.http_addr.as_str();
let call_id_first = "allow-network-first";
// Use the same urllib-based pattern as the other network integration tests,
// but point it at the runtime proxy directly so the blocked host reliably
// produces a network approval request without relying on curl.
let fetch_command = format!(
"python3 -c \"import urllib.request; proxy = urllib.request.ProxyHandler({{'http': 'http://{proxy_addr}'}}); opener = urllib.request.build_opener(proxy); print('OK:' + opener.open('http://codex-network-test.invalid', timeout=30).read().decode(errors='replace'))\""
);
// Use urllib without overriding proxy settings so managed-network sessions
// continue to exercise the env-based proxy routing path under bubblewrap.
let fetch_command =
"python3 -c \"import urllib.request; opener = urllib.request.build_opener(urllib.request.ProxyHandler()); print('OK:' + opener.open('http://codex-network-test.invalid', timeout=30).read().decode(errors='replace'))\""
.to_string();
let first_event = shell_event(
call_id_first,
&fetch_command,

View file

@ -12,29 +12,28 @@ into this binary.
**Current Behavior**
- Legacy Landlock + mount protections remain available as the legacy pipeline.
- The bubblewrap pipeline is standardized on the vendored path.
- During rollout, the bubblewrap pipeline is gated by the temporary feature
flag `use_linux_sandbox_bwrap` (CLI `-c` alias for
`features.use_linux_sandbox_bwrap`; legacy remains default when off).
- When enabled, the bubblewrap pipeline applies `PR_SET_NO_NEW_PRIVS` and a
- The default Linux sandbox pipeline is bubblewrap on the vendored path.
- Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`)
to force the legacy Landlock fallback.
- When the default bubblewrap pipeline is active, it applies `PR_SET_NO_NEW_PRIVS` and a
seccomp network filter in-process.
- When enabled, the filesystem is read-only by default via `--ro-bind / /`.
- When enabled, writable roots are layered with `--bind <root> <root>`.
- When enabled, protected subpaths under writable roots (for example `.git`,
- When the default bubblewrap pipeline is active, the filesystem is read-only by default via `--ro-bind / /`.
- When the default bubblewrap pipeline is active, writable roots are layered with `--bind <root> <root>`.
- When the default bubblewrap pipeline is active, protected subpaths under writable roots (for example `.git`,
resolved `gitdir:`, and `.codex`) are re-applied as read-only via `--ro-bind`.
- When enabled, symlink-in-path and non-existent protected paths inside
- When the default bubblewrap pipeline is active, symlink-in-path and non-existent protected paths inside
writable roots are blocked by mounting `/dev/null` on the symlink or first
missing component.
- When enabled, the helper explicitly isolates the user namespace via
- When the default bubblewrap pipeline is active, the helper explicitly isolates the user namespace via
`--unshare-user` and the PID namespace via `--unshare-pid`.
- When enabled and network is restricted without proxy routing, the helper also
- When the default bubblewrap pipeline is active and network is restricted without proxy routing, the helper also
isolates the network namespace via `--unshare-net`.
- In managed proxy mode, the helper uses `--unshare-net` plus an internal
TCP->UDS->TCP routing bridge so tool traffic reaches only configured proxy
endpoints.
- In managed proxy mode, after the bridge is live, seccomp blocks new
AF_UNIX/socketpair creation for the user command.
- When enabled, it mounts a fresh `/proc` via `--proc /proc` by default, but
- When the default bubblewrap pipeline is active, it mounts a fresh `/proc` via `--proc /proc` by default, but
you can skip this in restrictive container environments with `--no-proc`.
**Notes**

View file

@ -22,7 +22,8 @@ use codex_protocol::protocol::SandboxPolicy;
/// CLI surface for the Linux sandbox helper.
///
/// The type name remains `LandlockCommand` for compatibility with existing
/// wiring, but the filesystem sandbox now uses bubblewrap.
/// wiring, but bubblewrap is now the default filesystem sandbox and Landlock
/// is the legacy fallback.
pub struct LandlockCommand {
/// It is possible that the cwd used in the context of the sandbox policy
/// is different from the cwd of the process to spawn.
@ -42,11 +43,11 @@ pub struct LandlockCommand {
#[arg(long = "network-sandbox-policy", hide = true)]
pub network_sandbox_policy: Option<NetworkSandboxPolicy>,
/// Opt-in: use the bubblewrap-based Linux sandbox pipeline.
/// Opt-in: use the legacy Landlock Linux sandbox fallback.
///
/// When not set, we fall back to the legacy Landlock + mount pipeline.
#[arg(long = "use-bwrap-sandbox", hide = true, default_value_t = false)]
pub use_bwrap_sandbox: bool,
/// When not set, the helper uses the default bubblewrap pipeline.
#[arg(long = "use-legacy-landlock", hide = true, default_value_t = false)]
pub use_legacy_landlock: bool,
/// Internal: apply seccomp and `no_new_privs` in the already-sandboxed
/// process, then exec the user command.
@ -92,7 +93,7 @@ pub fn run_main() -> ! {
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
use_bwrap_sandbox,
use_legacy_landlock,
apply_seccomp_then_exec,
allow_network_for_proxy,
proxy_route_spec,
@ -103,7 +104,7 @@ pub fn run_main() -> ! {
if command.is_empty() {
panic!("No command specified to execute.");
}
ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec, use_bwrap_sandbox);
ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec, use_legacy_landlock);
let EffectiveSandboxPolicies {
sandbox_policy,
file_system_sandbox_policy,
@ -154,7 +155,7 @@ pub fn run_main() -> ! {
exec_or_panic(command);
}
if use_bwrap_sandbox {
if !use_legacy_landlock {
// Outer stage: bubblewrap first, then re-enter this binary in the
// sandboxed environment to apply seccomp. This path never falls back
// to legacy Landlock on failure.
@ -171,7 +172,6 @@ pub fn run_main() -> ! {
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy,
use_bwrap_sandbox,
allow_network_for_proxy,
proxy_route_spec,
command,
@ -256,9 +256,9 @@ fn resolve_sandbox_policies(
}
}
fn ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec: bool, use_bwrap_sandbox: bool) {
if apply_seccomp_then_exec && !use_bwrap_sandbox {
panic!("--apply-seccomp-then-exec requires --use-bwrap-sandbox");
fn ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec: bool, use_legacy_landlock: bool) {
if apply_seccomp_then_exec && use_legacy_landlock {
panic!("--apply-seccomp-then-exec is incompatible with --use-legacy-landlock");
}
}
@ -280,7 +280,8 @@ fn run_bwrap_with_proc_fallback(
network_mode,
)
{
eprintln!("codex-linux-sandbox: bwrap could not mount /proc; retrying with --no-proc");
// Keep the retry silent so sandbox-internal diagnostics do not leak into the
// child process stderr stream.
mount_proc = false;
}
@ -470,7 +471,6 @@ struct InnerSeccompCommandArgs<'a> {
sandbox_policy: &'a SandboxPolicy,
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
use_bwrap_sandbox: bool,
allow_network_for_proxy: bool,
proxy_route_spec: Option<String>,
command: Vec<String>,
@ -483,7 +483,6 @@ fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String>
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
use_bwrap_sandbox,
allow_network_for_proxy,
proxy_route_spec,
command,
@ -516,10 +515,7 @@ fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String>
"--network-sandbox-policy".to_string(),
network_policy_json,
];
if use_bwrap_sandbox {
inner.push("--use-bwrap-sandbox".to_string());
inner.push("--apply-seccomp-then-exec".to_string());
}
inner.push("--apply-seccomp-then-exec".to_string());
if allow_network_for_proxy {
inner.push("--allow-network-for-proxy".to_string());
let proxy_route_spec = proxy_route_spec

View file

@ -127,7 +127,6 @@ fn managed_proxy_inner_command_includes_route_spec() {
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
use_bwrap_sandbox: true,
allow_network_for_proxy: true,
proxy_route_spec: Some("{\"routes\":[]}".to_string()),
command: vec!["/bin/true".to_string()],
@ -145,7 +144,6 @@ fn inner_command_includes_split_policy_flags() {
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
use_bwrap_sandbox: true,
allow_network_for_proxy: false,
proxy_route_spec: None,
command: vec!["/bin/true".to_string()],
@ -163,7 +161,6 @@ fn non_managed_inner_command_omits_route_spec() {
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
use_bwrap_sandbox: true,
allow_network_for_proxy: false,
proxy_route_spec: None,
command: vec!["/bin/true".to_string()],
@ -181,7 +178,6 @@ fn managed_proxy_inner_command_requires_route_spec() {
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
use_bwrap_sandbox: true,
allow_network_for_proxy: true,
proxy_route_spec: None,
command: vec!["/bin/true".to_string()],
@ -244,8 +240,8 @@ fn resolve_sandbox_policies_rejects_partial_split_policies() {
}
#[test]
fn apply_seccomp_then_exec_without_bwrap_panics() {
let result = std::panic::catch_unwind(|| ensure_inner_stage_mode_is_valid(true, false));
fn apply_seccomp_then_exec_with_legacy_landlock_panics() {
let result = std::panic::catch_unwind(|| ensure_inner_stage_mode_is_valid(true, true));
assert!(result.is_err());
}
@ -253,5 +249,5 @@ fn apply_seccomp_then_exec_without_bwrap_panics() {
fn valid_inner_stage_modes_do_not_panic() {
ensure_inner_stage_mode_is_valid(false, false);
ensure_inner_stage_mode_is_valid(false, true);
ensure_inner_stage_mode_is_valid(true, true);
ensure_inner_stage_mode_is_valid(true, false);
}

View file

@ -72,7 +72,7 @@ async fn run_cmd_result_with_writable_roots(
cmd: &[&str],
writable_roots: &[PathBuf],
timeout_ms: u64,
use_bwrap_sandbox: bool,
use_legacy_landlock: bool,
network_access: bool,
) -> Result<codex_core::exec::ExecToolCallOutput> {
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
@ -96,7 +96,7 @@ async fn run_cmd_result_with_writable_roots(
file_system_sandbox_policy,
network_sandbox_policy,
timeout_ms,
use_bwrap_sandbox,
use_legacy_landlock,
)
.await
}
@ -108,7 +108,7 @@ async fn run_cmd_result_with_policies(
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
timeout_ms: u64,
use_bwrap_sandbox: bool,
use_legacy_landlock: bool,
) -> Result<codex_core::exec::ExecToolCallOutput> {
let cwd = std::env::current_dir().expect("cwd should exist");
let sandbox_cwd = cwd.clone();
@ -133,7 +133,7 @@ async fn run_cmd_result_with_policies(
network_sandbox_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
use_bwrap_sandbox,
use_legacy_landlock,
None,
)
.await
@ -155,7 +155,7 @@ async fn should_skip_bwrap_tests() -> bool {
&["bash", "-lc", "true"],
&[],
NETWORK_TIMEOUT_MS,
true,
false,
true,
)
.await
@ -216,7 +216,7 @@ async fn test_dev_null_write() {
// We have seen timeouts when running this test in CI on GitHub,
// so we are using a generous timeout until we can diagnose further.
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await
@ -240,7 +240,7 @@ async fn bwrap_populates_minimal_dev_nodes() {
],
&[],
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await
@ -278,7 +278,7 @@ async fn bwrap_preserves_writable_dev_shm_bind_mount() {
],
&[PathBuf::from("/dev/shm")],
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await
@ -442,7 +442,7 @@ async fn sandbox_blocks_git_and_codex_writes_inside_writable_root() {
],
&[tmpdir.path().to_path_buf()],
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await,
@ -458,7 +458,7 @@ async fn sandbox_blocks_git_and_codex_writes_inside_writable_root() {
],
&[tmpdir.path().to_path_buf()],
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await,
@ -495,7 +495,7 @@ async fn sandbox_blocks_codex_symlink_replacement_attack() {
],
&[tmpdir.path().to_path_buf()],
LONG_TIMEOUT_MS,
true,
false,
true,
)
.await,
@ -548,7 +548,7 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
file_system_sandbox_policy,
NetworkSandboxPolicy::Enabled,
LONG_TIMEOUT_MS,
true,
false,
)
.await,
"explicit split-policy carveout should be denied under bubblewrap",
@ -599,7 +599,7 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
file_system_sandbox_policy,
NetworkSandboxPolicy::Enabled,
LONG_TIMEOUT_MS,
true,
false,
)
.await,
"root-read carveout should be denied under bubblewrap",

View file

@ -133,7 +133,6 @@ async fn run_linux_sandbox_direct(
cwd.to_string_lossy().to_string(),
"--sandbox-policy".to_string(),
policy_json,
"--use-bwrap-sandbox".to_string(),
];
if allow_network_for_proxy {
args.push("--allow-network-for-proxy".to_string());

View file

@ -89,8 +89,8 @@ impl CliConfigOverrides {
}
fn canonicalize_override_key(key: &str) -> String {
if key == "use_linux_sandbox_bwrap" {
"features.use_linux_sandbox_bwrap".to_string()
if key == "use_legacy_landlock" {
"features.use_legacy_landlock".to_string()
} else {
key.to_string()
}
@ -181,12 +181,12 @@ mod tests {
}
#[test]
fn canonicalizes_use_linux_sandbox_bwrap_alias() {
fn canonicalizes_use_legacy_landlock_alias() {
let overrides = CliConfigOverrides {
raw_overrides: vec!["use_linux_sandbox_bwrap=true".to_string()],
raw_overrides: vec!["use_legacy_landlock=true".to_string()],
};
let parsed = overrides.parse_overrides().expect("parse_overrides");
assert_eq!(parsed[0].0.as_str(), "features.use_linux_sandbox_bwrap");
assert_eq!(parsed[0].0.as_str(), "features.use_legacy_landlock");
assert_eq!(parsed[0].1.as_bool(), Some(true));
}