Skip to main content

codex/cli/
sandbox.rs

1use crate::{ConfigOverride, FeatureToggles};
2use std::{ffi::OsString, path::PathBuf, process::ExitStatus};
3
4/// Sandbox platform variant; maps to platform subcommands of `codex sandbox`.
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum SandboxPlatform {
7    Macos,
8    Linux,
9    Windows,
10}
11
12impl SandboxPlatform {
13    pub(crate) fn subcommand(self) -> &'static str {
14        match self {
15            SandboxPlatform::Macos => "macos",
16            SandboxPlatform::Linux => "linux",
17            SandboxPlatform::Windows => "windows",
18        }
19    }
20}
21
22/// Request to run an arbitrary command inside a Codex-provided sandbox.
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct SandboxCommandRequest {
25    /// Target platform subcommand; maps to `macos` (alias `seatbelt`), `linux` (alias `landlock`), or `windows`.
26    pub platform: SandboxPlatform,
27    /// Trailing command arguments to execute. Must be non-empty to avoid the upstream CLI panic.
28    pub command: Vec<OsString>,
29    /// Request the workspace-write sandbox preset (`--full-auto`).
30    pub full_auto: bool,
31    /// Stream macOS sandbox denials after the child process exits (no-op on other platforms).
32    pub log_denials: bool,
33    /// Allow Unix sockets on macOS (`--allow-unix-socket`).
34    pub allow_unix_socket: bool,
35    /// Include Codex-managed config in the sandbox environment.
36    pub include_managed_config: bool,
37    /// Optional named permissions profile passed via `--permissions-profile`.
38    pub permissions_profile: Option<String>,
39    /// Additional `--config key=value` overrides to pass through.
40    pub config_overrides: Vec<ConfigOverride>,
41    /// Feature toggles forwarded to `--enable`/`--disable`.
42    pub feature_toggles: FeatureToggles,
43    /// Working directory for the spawned command; falls back to the builder value, then the current process directory.
44    pub working_dir: Option<PathBuf>,
45}
46
47impl SandboxCommandRequest {
48    pub fn new<I, S>(platform: SandboxPlatform, command: I) -> Self
49    where
50        I: IntoIterator<Item = S>,
51        S: Into<OsString>,
52    {
53        Self {
54            platform,
55            command: command.into_iter().map(Into::into).collect(),
56            full_auto: false,
57            log_denials: false,
58            allow_unix_socket: false,
59            include_managed_config: false,
60            permissions_profile: None,
61            config_overrides: Vec::new(),
62            feature_toggles: FeatureToggles::default(),
63            working_dir: None,
64        }
65    }
66
67    pub fn full_auto(mut self, enable: bool) -> Self {
68        self.full_auto = enable;
69        self
70    }
71
72    pub fn log_denials(mut self, enable: bool) -> Self {
73        self.log_denials = enable;
74        self
75    }
76
77    pub fn allow_unix_socket(mut self, enable: bool) -> Self {
78        self.allow_unix_socket = enable;
79        self
80    }
81
82    pub fn include_managed_config(mut self, enable: bool) -> Self {
83        self.include_managed_config = enable;
84        self
85    }
86
87    pub fn permissions_profile(mut self, profile: impl Into<String>) -> Self {
88        let profile = profile.into();
89        self.permissions_profile = (!profile.trim().is_empty()).then_some(profile);
90        self
91    }
92
93    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
94        self.config_overrides.push(ConfigOverride::new(key, value));
95        self
96    }
97
98    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
99        self.config_overrides.push(ConfigOverride::from_raw(raw));
100        self
101    }
102
103    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
104        self.feature_toggles.enable.push(name.into());
105        self
106    }
107
108    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
109        self.feature_toggles.disable.push(name.into());
110        self
111    }
112
113    pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
114        self.working_dir = Some(dir.into());
115        self
116    }
117}
118
119/// Captured output from `codex sandbox <platform>`.
120#[derive(Clone, Debug)]
121pub struct SandboxRun {
122    /// Exit status returned by the inner command (mirrors the sandbox helper).
123    pub status: ExitStatus,
124    /// Captured stdout (mirrored to the console when `mirror_stdout` is true).
125    pub stdout: String,
126    /// Captured stderr (mirrored unless `quiet` is set).
127    pub stderr: String,
128}