Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ jobs:
- name: bazel test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }}
shell: bash
run: |
bazel $BAZEL_STARTUP_ARGS --bazelrc=.github/workflows/ci.bazelrc test //... \
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ jobs:
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
# Keep cargo-based CI independent of system bwrap build deps.
# The bwrap FFI path is validated in Bazel workflows.
CODEX_BWRAP_ENABLE_FFI: "0"

strategy:
fail-fast: false
Expand Down Expand Up @@ -467,6 +470,9 @@ jobs:
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
# Keep cargo-based CI independent of system bwrap build deps.
# The bwrap FFI path is validated in Bazel workflows.
CODEX_BWRAP_ENABLE_FFI: "0"

strategy:
fail-fast: false
Expand Down Expand Up @@ -502,7 +508,6 @@ jobs:

steps:
- uses: actions/checkout@v6

# Some integration tests rely on DotSlash being installed.
# See https://github.com/openai/codex/pull/7617.
- name: Install DotSlash
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
defaults:
run:
working-directory: codex-rs
env:
CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }}

strategy:
fail-fast: false
Expand All @@ -89,6 +91,13 @@ jobs:

steps:
- uses: actions/checkout@v6
- name: Install Linux bwrap build dependencies
if: ${{ runner.os == 'Linux' }}
shell: bash
run: |
set -euo pipefail
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
- name: Install UBSan runtime (musl)
if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
shell: bash
Expand Down
1 change: 0 additions & 1 deletion codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,13 +1484,15 @@ impl CodexMessageProcessor {
let outgoing = self.outgoing.clone();
let req_id = request_id;
let sandbox_cwd = self.config.cwd.clone();
let use_linux_sandbox_bwrap = self.config.features.enabled(Feature::UseLinuxSandboxBwrap);

tokio::spawn(async move {
match codex_core::exec::process_exec_tool_call(
exec_params,
&effective_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
None,
)
.await
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/cli/src/debug_sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,16 +227,19 @@ 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);
spawn_command_under_linux_sandbox(
codex_linux_sandbox_exe,
command,
cwd,
config.sandbox_policy.get(),
sandbox_policy_cwd.as_path(),
use_bwrap_sandbox,
stdio_policy,
env,
)
Expand Down
20 changes: 19 additions & 1 deletion codex-rs/common/src/config_override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl CliConfigOverrides {
}
};

Ok((key.to_string(), value))
Ok((canonicalize_override_key(key), value))
})
.collect()
}
Expand All @@ -88,6 +88,14 @@ impl CliConfigOverrides {
}
}

fn canonicalize_override_key(key: &str) -> String {
if key == "use_linux_sandbox_bwrap" {
"features.use_linux_sandbox_bwrap".to_string()
} else {
key.to_string()
}
}

/// Apply a single override onto `root`, creating intermediate objects as
/// necessary.
fn apply_single_override(root: &mut Value, path: &str, value: Value) {
Expand Down Expand Up @@ -172,6 +180,16 @@ mod tests {
assert_eq!(arr.len(), 3);
}

#[test]
fn canonicalizes_use_linux_sandbox_bwrap_alias() {
let overrides = CliConfigOverrides {
raw_overrides: vec!["use_linux_sandbox_bwrap=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].1.as_bool(), Some(true));
}

#[test]
fn parses_inline_table() {
let v = parse_toml_value("{a = 1, b = 2}").expect("parse");
Expand Down
6 changes: 6 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@
"unified_exec": {
"type": "boolean"
},
"use_linux_sandbox_bwrap": {
"type": "boolean"
},
"web_search": {
"type": "boolean"
},
Expand Down Expand Up @@ -1253,6 +1256,9 @@
"unified_exec": {
"type": "boolean"
},
"use_linux_sandbox_bwrap": {
"type": "boolean"
},
"web_search": {
"type": "boolean"
},
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ pub(crate) struct TurnContext {
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
pub(crate) tools_config: ToolsConfig,
pub(crate) features: Features,
pub(crate) ghost_snapshot: GhostSnapshotConfig,
pub(crate) final_output_json_schema: Option<Value>,
pub(crate) codex_linux_sandbox_exe: Option<PathBuf>,
Expand Down Expand Up @@ -766,6 +767,7 @@ impl Session {
windows_sandbox_level: session_configuration.windows_sandbox_level,
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
tools_config,
features: per_turn_config.features.clone(),
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
final_output_json_schema: None,
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
Expand Down Expand Up @@ -1036,6 +1038,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),
};
let cancel_token = sess.mcp_startup_cancellation_token().await;

Expand Down Expand Up @@ -1285,6 +1288,9 @@ impl Session {
sandbox_policy: per_turn_config.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),
};
if let Err(e) = self
.services
Expand Down Expand Up @@ -2367,6 +2373,7 @@ impl Session {
sandbox_policy: turn_context.sandbox_policy.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),
};
let cancel_token = self.reset_mcp_startup_cancellation_token().await;

Expand Down Expand Up @@ -3343,6 +3350,7 @@ async fn spawn_review_thread(
session_source,
transport_manager,
tools_config,
features: parent_turn_context.features.clone(),
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
developer_instructions: None,
user_instructions: None,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/connectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub async fn list_accessible_connectors_from_mcp_tools(
sandbox_policy: SandboxPolicy::ReadOnly,
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),
};

mcp_connection_manager
Expand Down
15 changes: 9 additions & 6 deletions codex-rs/core/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub async fn process_exec_tool_call(
sandbox_policy: &SandboxPolicy,
sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_linux_sandbox_bwrap: bool,
stdout_stream: Option<StdoutStream>,
) -> Result<ExecToolCallOutput> {
let windows_sandbox_level = params.windows_sandbox_level;
Expand Down Expand Up @@ -184,14 +185,15 @@ pub async fn process_exec_tool_call(

let manager = SandboxManager::new();
let exec_env = manager
.transform(
.transform(crate::sandboxing::SandboxTransformRequest {
spec,
sandbox_policy,
sandbox_type,
sandbox_cwd,
codex_linux_sandbox_exe.as_ref(),
policy: sandbox_policy,
sandbox: sandbox_type,
sandbox_policy_cwd: sandbox_cwd,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(),
use_linux_sandbox_bwrap,
windows_sandbox_level,
)
})
.map_err(CodexErr::from)?;

// Route through the sandboxing module for a single, unified execution path.
Expand Down Expand Up @@ -1108,6 +1110,7 @@ mod tests {
&SandboxPolicy::DangerFullAccess,
cwd.as_path(),
&None,
false,
None,
)
.await;
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub enum Feature {
WebSearchCached,
/// Gate the execpolicy enforcement for shell/unified exec.
ExecPolicy,
/// Use the bubblewrap-based Linux sandbox pipeline.
UseLinuxSandboxBwrap,
/// Allow the model to request approval and propose exec rules.
RequestRule,
/// Enable Windows sandbox (restricted token) on Windows.
Expand Down Expand Up @@ -465,6 +467,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment,
default_enabled: true,
},
FeatureSpec {
id: Feature::UseLinuxSandboxBwrap,
key: "use_linux_sandbox_bwrap",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::RequestRule,
key: "request_rule",
Expand Down
53 changes: 47 additions & 6 deletions codex-rs/core/src/landlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,34 @@ use std::path::Path;
use std::path::PathBuf;
use tokio::process::Child;

/// Spawn a shell tool command under the Linux Landlock+seccomp sandbox helper
/// (codex-linux-sandbox).
/// Spawn a shell tool command under the Linux sandbox helper
/// (codex-linux-sandbox), which currently uses bubblewrap for filesystem
/// 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.
#[allow(clippy::too_many_arguments)]
pub async fn spawn_command_under_linux_sandbox<P>(
codex_linux_sandbox_exe: P,
command: Vec<String>,
command_cwd: PathBuf,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
stdio_policy: StdioPolicy,
env: HashMap<String, String>,
) -> std::io::Result<Child>
where
P: AsRef<Path>,
{
let args = create_linux_sandbox_command_args(command, sandbox_policy, sandbox_policy_cwd);
let args = create_linux_sandbox_command_args(
command,
sandbox_policy,
sandbox_policy_cwd,
use_bwrap_sandbox,
);
let arg0 = Some("codex-linux-sandbox");
spawn_child_async(
codex_linux_sandbox_exe.as_ref().to_path_buf(),
Expand All @@ -40,10 +48,14 @@ where
}

/// Converts the sandbox policy 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(
command: Vec<String>,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
use_bwrap_sandbox: bool,
) -> Vec<String> {
#[expect(clippy::expect_used)]
let sandbox_policy_cwd = sandbox_policy_cwd
Expand All @@ -60,13 +72,42 @@ pub(crate) fn create_linux_sandbox_command_args(
sandbox_policy_cwd,
"--sandbox-policy".to_string(),
sandbox_policy_json,
// Separator so that command arguments starting with `-` are not parsed as
// options of the helper itself.
"--".to_string(),
];
if use_bwrap_sandbox {
linux_cmd.push("--use-bwrap-sandbox".to_string());
}

// Separator so that command arguments starting with `-` are not parsed as
// options of the helper itself.
linux_cmd.push("--".to_string());

// Append the original tool command.
linux_cmd.extend(command);

linux_cmd
}

#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn bwrap_flags_are_feature_gated() {
let command = vec!["/bin/true".to_string()];
let cwd = Path::new("/tmp");
let policy = SandboxPolicy::ReadOnly;

let with_bwrap = create_linux_sandbox_command_args(command.clone(), &policy, cwd, true);
assert_eq!(
with_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
true
);

let without_bwrap = create_linux_sandbox_command_args(command, &policy, cwd, false);
assert_eq!(
without_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
false
);
}
}
1 change: 1 addition & 0 deletions codex-rs/core/src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
sandbox_policy: SandboxPolicy::ReadOnly,
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),
};

mcp_connection_manager
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/mcp_connection_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ pub struct SandboxState {
pub sandbox_policy: SandboxPolicy,
pub codex_linux_sandbox_exe: Option<PathBuf>,
pub sandbox_cwd: PathBuf,
#[serde(default)]
pub use_linux_sandbox_bwrap: bool,
}

/// A thin wrapper around a set of running [`RmcpClient`] instances.
Expand Down
Loading
Loading