mirror of
https://github.com/openai/codex.git
synced 2026-05-23 04:24:21 +00:00
cli: infer host sandbox backend (#24102)
## Why `codex sandbox` previously required an OS subcommand like `linux`, `macos`, or `windows`, even though the command can only run the sandbox backend available on the current host. That made the CLI imply a cross-OS choice that does not exist. ## What changed - Collapse `codex sandbox <os>` into `codex sandbox [COMMAND]...` by wiring the `sandbox` parser directly to the host-specific backend args with `cfg`. - Keep the existing backend runners for Seatbelt, Linux sandbox, and Windows restricted token. - Rename the public Windows debug sandbox runner to `run_command_under_windows_sandbox` for clarity. - Update the Rust sandbox docs and related README references to describe host OS selection and avoid pointing readers at legacy `sandbox_mode` config. ## Arg0 compatibility The `codex-linux-sandbox` helper path is still handled before normal CLI parsing. `arg0_dispatch()` checks whether the executable basename is `codex-linux-sandbox` and directly calls `codex_linux_sandbox::run_main()`, so removing the `sandbox linux` parser branch does not affect the arg0 helper flow. ## Verification - `cargo test -p codex-cli` - `cargo test -p codex-arg0` - `just fix -p codex-cli`
This commit is contained in:
@@ -55,26 +55,17 @@ Use `codex exec --ephemeral ...` to run without persisting session rollout files
|
||||
|
||||
### Experimenting with the Codex Sandbox
|
||||
|
||||
To test to see what happens when a command is run under the sandbox provided by Codex, we provide the following subcommands in Codex CLI:
|
||||
To test to see what happens when a command is run under the sandbox provided by Codex, use the `sandbox` subcommand in Codex CLI:
|
||||
|
||||
```
|
||||
# macOS
|
||||
codex sandbox macos [--log-denials] [COMMAND]...
|
||||
# Uses the sandbox implementation for the current host OS:
|
||||
# Seatbelt on macOS, the Linux sandbox on Linux, and Windows restricted token on Windows.
|
||||
codex sandbox [COMMAND]...
|
||||
|
||||
# Linux
|
||||
codex sandbox linux [COMMAND]...
|
||||
|
||||
# Windows
|
||||
codex sandbox windows [COMMAND]...
|
||||
|
||||
# Legacy aliases
|
||||
codex debug seatbelt [--log-denials] [COMMAND]...
|
||||
codex debug landlock [COMMAND]...
|
||||
# macOS-only diagnostic option
|
||||
codex sandbox --log-denials [COMMAND]...
|
||||
```
|
||||
|
||||
To try a writable legacy sandbox mode with these commands, pass an explicit config override such
|
||||
as `-c 'sandbox_mode="workspace-write"'`.
|
||||
|
||||
### Selecting a sandbox policy via `--sandbox`
|
||||
|
||||
The Rust CLI exposes a dedicated `--sandbox` (`-s`) flag that lets you pick the sandbox policy **without** having to reach for the generic `-c/--config` option:
|
||||
@@ -90,7 +81,6 @@ codex --sandbox workspace-write
|
||||
codex --sandbox danger-full-access
|
||||
```
|
||||
|
||||
The same setting can be persisted in `~/.codex/config.toml` via the top-level `sandbox_mode = "MODE"` key, e.g. `sandbox_mode = "workspace-write"`.
|
||||
In `workspace-write`, Codex also includes `~/.codex/memories` in its writable roots so memory maintenance does not require an extra approval.
|
||||
|
||||
## Code Organization
|
||||
|
||||
@@ -110,7 +110,7 @@ pub async fn run_command_under_landlock(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_command_under_windows(
|
||||
pub async fn run_command_under_windows_sandbox(
|
||||
command: WindowsCommand,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -672,8 +672,7 @@ async fn load_debug_sandbox_config_with_codex_home(
|
||||
// For legacy configs, `codex sandbox` historically defaulted to read-only
|
||||
// instead of inheriting ambient `sandbox_mode` settings from user/system
|
||||
// config. Keep that behavior unless this invocation explicitly passes a
|
||||
// legacy `sandbox_mode` CLI override, which is now the documented writable
|
||||
// replacement for the removed `--full-auto` flag.
|
||||
// legacy `sandbox_mode` CLI override for compatibility with older callers.
|
||||
let uses_legacy_sandbox_mode_override = cli_overrides_use_legacy_sandbox_mode(&cli_overrides);
|
||||
let config = build_debug_sandbox_config(
|
||||
cli_overrides.clone(),
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::path::PathBuf;
|
||||
|
||||
pub use debug_sandbox::run_command_under_landlock;
|
||||
pub use debug_sandbox::run_command_under_seatbelt;
|
||||
pub use debug_sandbox::run_command_under_windows;
|
||||
pub use debug_sandbox::run_command_under_windows_sandbox;
|
||||
pub use login::read_access_token_from_stdin;
|
||||
pub use login::read_api_key_from_stdin;
|
||||
pub use login::run_login_status;
|
||||
@@ -20,8 +20,8 @@ pub use login::run_login_with_device_code;
|
||||
pub use login::run_login_with_device_code_fallback_to_browser;
|
||||
pub use login::run_logout;
|
||||
|
||||
// TODO: Deduplicate these shared sandbox options if we remove the explicit
|
||||
// `codex sandbox <os>` platform subcommands.
|
||||
// These command structs share common sandbox options, but remain separate
|
||||
// because each host backend has a slightly different option surface.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SeatbeltCommand {
|
||||
/// Named permissions profile to apply from the active configuration stack.
|
||||
|
||||
@@ -10,9 +10,6 @@ use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_arg0::arg0_dispatch_or_else;
|
||||
use codex_chatgpt::apply_command::ApplyCommand;
|
||||
use codex_chatgpt::apply_command::run_apply_command;
|
||||
use codex_cli::LandlockCommand;
|
||||
use codex_cli::SeatbeltCommand;
|
||||
use codex_cli::WindowsCommand;
|
||||
use codex_cli::read_access_token_from_stdin;
|
||||
use codex_cli::read_api_key_from_stdin;
|
||||
use codex_cli::run_login_status;
|
||||
@@ -160,7 +157,7 @@ enum Subcommand {
|
||||
Doctor(DoctorCommand),
|
||||
|
||||
/// Run commands within a Codex-provided sandbox.
|
||||
Sandbox(SandboxArgs),
|
||||
Sandbox(HostSandboxArgs),
|
||||
|
||||
/// Debugging tools.
|
||||
Debug(DebugCommand),
|
||||
@@ -343,24 +340,25 @@ struct ForkCommand {
|
||||
config_overrides: TuiCli,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
type HostSandboxArgs = codex_cli::SeatbeltCommand;
|
||||
#[cfg(target_os = "linux")]
|
||||
type HostSandboxArgs = codex_cli::LandlockCommand;
|
||||
#[cfg(target_os = "windows")]
|
||||
type HostSandboxArgs = codex_cli::WindowsCommand;
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
type HostSandboxArgs = UnsupportedSandboxArgs;
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
#[derive(Debug, Parser)]
|
||||
struct SandboxArgs {
|
||||
#[command(subcommand)]
|
||||
cmd: SandboxCommand,
|
||||
}
|
||||
struct UnsupportedSandboxArgs {
|
||||
#[clap(skip)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
enum SandboxCommand {
|
||||
/// Run a command under Seatbelt (macOS only).
|
||||
#[clap(visible_alias = "seatbelt")]
|
||||
Macos(SeatbeltCommand),
|
||||
|
||||
/// Run a command under the Linux sandbox (bubblewrap by default).
|
||||
#[clap(visible_alias = "landlock")]
|
||||
Linux(LandlockCommand),
|
||||
|
||||
/// Run a command under Windows restricted token (Windows only).
|
||||
Windows(WindowsCommand),
|
||||
/// Full command args to run under the host sandbox.
|
||||
#[arg(trailing_var_arg = true)]
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -1238,56 +1236,37 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
codex_cloud_tasks::run_main(cloud_cli, arg0_paths.codex_linux_sandbox_exe.clone())
|
||||
.await?;
|
||||
}
|
||||
Some(Subcommand::Sandbox(sandbox_args)) => match sandbox_args.cmd {
|
||||
SandboxCommand::Macos(mut seatbelt_cli) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox macos",
|
||||
)?;
|
||||
prepend_config_flags(
|
||||
&mut seatbelt_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
codex_cli::run_command_under_seatbelt(
|
||||
seatbelt_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
SandboxCommand::Linux(mut landlock_cli) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox linux",
|
||||
)?;
|
||||
prepend_config_flags(
|
||||
&mut landlock_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
codex_cli::run_command_under_landlock(
|
||||
landlock_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
SandboxCommand::Windows(mut windows_cli) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox windows",
|
||||
)?;
|
||||
prepend_config_flags(
|
||||
&mut windows_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
codex_cli::run_command_under_windows(
|
||||
windows_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
Some(Subcommand::Sandbox(mut sandbox_cli)) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox",
|
||||
)?;
|
||||
prepend_config_flags(
|
||||
&mut sandbox_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
#[cfg(target_os = "macos")]
|
||||
codex_cli::run_command_under_seatbelt(
|
||||
sandbox_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
#[cfg(target_os = "linux")]
|
||||
codex_cli::run_command_under_landlock(
|
||||
sandbox_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
#[cfg(target_os = "windows")]
|
||||
codex_cli::run_command_under_windows_sandbox(
|
||||
sandbox_cli,
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
anyhow::bail!("`codex sandbox` is not supported on this operating system");
|
||||
}
|
||||
Some(Subcommand::Debug(DebugCommand { subcommand })) => match subcommand {
|
||||
DebugSubcommand::Models(cmd) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
@@ -2500,12 +2479,12 @@ mod tests {
|
||||
assert!(matches!(cli.subcommand, Some(Subcommand::Update)));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[test]
|
||||
fn sandbox_macos_parses_permissions_profile() {
|
||||
fn sandbox_parses_permissions_profile() {
|
||||
let cli = MultitoolCli::try_parse_from([
|
||||
"codex",
|
||||
"sandbox",
|
||||
"macos",
|
||||
"--permissions-profile",
|
||||
":workspace",
|
||||
"--",
|
||||
@@ -2513,20 +2492,18 @@ mod tests {
|
||||
])
|
||||
.expect("parse");
|
||||
|
||||
let Some(Subcommand::Sandbox(SandboxArgs {
|
||||
cmd: SandboxCommand::Macos(command),
|
||||
})) = cli.subcommand
|
||||
else {
|
||||
panic!("expected sandbox macos command");
|
||||
let Some(Subcommand::Sandbox(command)) = cli.subcommand else {
|
||||
panic!("expected sandbox command");
|
||||
};
|
||||
|
||||
assert_eq!(command.permissions_profile.as_deref(), Some(":workspace"));
|
||||
assert_eq!(command.command, vec!["echo"]);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[test]
|
||||
fn sandbox_macos_rejects_explicit_profile_controls_without_profile() {
|
||||
let err = MultitoolCli::try_parse_from(["codex", "sandbox", "macos", "-C", "/tmp"])
|
||||
fn sandbox_rejects_explicit_profile_controls_without_profile() {
|
||||
let err = MultitoolCli::try_parse_from(["codex", "sandbox", "-C", "/tmp"])
|
||||
.expect_err("parse should fail");
|
||||
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
||||
@@ -2579,8 +2556,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn sandbox_full_auto_no_longer_parses() {
|
||||
let result =
|
||||
MultitoolCli::try_parse_from(["codex", "sandbox", "linux", "--full-auto", "--"]);
|
||||
let result = MultitoolCli::try_parse_from(["codex", "sandbox", "--full-auto", "--"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ Seatbelt also keeps the legacy default preferences read access
|
||||
|
||||
### Linux
|
||||
|
||||
Expects the binary containing `codex-core` to run the equivalent of `codex sandbox linux` (legacy alias: `codex debug landlock`) when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details.
|
||||
Expects the binary containing `codex-core` to run the equivalent of `codex sandbox` when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details.
|
||||
|
||||
Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on Linux.
|
||||
They can continue to use the legacy Landlock path when the split filesystem
|
||||
|
||||
@@ -94,4 +94,4 @@ commands that would enter the bubblewrap path.
|
||||
you can skip this in restrictive container environments with `--no-proc`.
|
||||
|
||||
**Notes**
|
||||
- The CLI surface still uses legacy names like `codex debug landlock`.
|
||||
- The CLI surface is `codex sandbox`; the host OS selects the sandbox backend.
|
||||
|
||||
Reference in New Issue
Block a user