Wire the Windows MXC sandbox into command execution (#45176)

## What changed

- Add explicit MXC backend selection and carry its identity through exec-server process reporting and sandbox violation classification.
- Launch MXC through the Codex executable with the effective permission profile and command environment.
- Reject exec-server MXC requests when native MXC is unavailable or when they request a TTY, an `arg0` override, or managed networking. Reject private desktop isolation during MXC preparation.
- Allow an explicitly empty child environment and avoid exposing request payload values in launcher decode errors.

## Testing

Add coverage for sandbox selection and unsupported-request rejection, plus Windows RPC tests for stdin writes and temporary-directory permissions derived from the command environment. Native MXC tests skip when MXC is unavailable.

GitOrigin-RevId: 3626ff0f9ad7f9b812ce09b68c31ea9a5a9c72b1
This commit is contained in:
iceweasel-oai authored and copyberry committed 2026-09-13 06:07:03 +00:00
1 parent dfaf451426
commit c379459bba
37 files changed
+422 -85

No files matched your search

@@ -315,6 +315,7 @@ impl CommandExecRequestProcessor {
&sandbox_cwd, &sandbox_cwd,
windows_sandbox_workspace_roots.as_slice(), windows_sandbox_workspace_roots.as_slice(),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
&self.arg0_paths.codex_self_exe,
use_legacy_landlock, use_legacy_landlock,
) )
.map_err(|err| internal_error(format!("exec failed: {err}")))?; .map_err(|err| internal_error(format!("exec failed: {err}")))?;
@@ -4907,7 +4907,7 @@ impl ThreadRequestProcessor {
serde_json::json!("unelevated"), serde_json::json!("unelevated"),
); );
} }
WindowsSandboxLevel::Disabled => {} WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => {}
} }
} }
let request_overrides = if cli_overrides.is_empty() { let request_overrides = if cli_overrides.is_empty() {
@@ -222,7 +222,9 @@ fn determine_windows_sandbox_readiness_from_state(
) -> WindowsSandboxReadinessResponse { ) -> WindowsSandboxReadinessResponse {
let status = match windows_sandbox_level { let status = match windows_sandbox_level {
WindowsSandboxLevel::Disabled => WindowsSandboxReadiness::NotConfigured, WindowsSandboxLevel::Disabled => WindowsSandboxReadiness::NotConfigured,
WindowsSandboxLevel::RestrictedToken => WindowsSandboxReadiness::Ready, WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Mxc => {
WindowsSandboxReadiness::Ready
}
WindowsSandboxLevel::Elevated => { WindowsSandboxLevel::Elevated => {
if sandbox_setup_is_complete { if sandbox_setup_is_complete {
WindowsSandboxReadiness::Ready WindowsSandboxReadiness::Ready
+1 -1
View File
@@ -3329,7 +3329,7 @@ impl Config {
match WindowsSandboxLevel::from_features(&features) { match WindowsSandboxLevel::from_features(&features) {
WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated), WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated),
WindowsSandboxLevel::RestrictedToken => Some(WindowsSandboxModeToml::Unelevated), WindowsSandboxLevel::RestrictedToken => Some(WindowsSandboxModeToml::Unelevated),
WindowsSandboxLevel::Disabled => None, WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => None,
} }
}); });
apply_requirement_constrained_value( apply_requirement_constrained_value(
+8 -1
View File
@@ -300,6 +300,7 @@ pub async fn process_exec_tool_call(
sandbox_cwd: &AbsolutePathBuf, sandbox_cwd: &AbsolutePathBuf,
windows_sandbox_workspace_roots: &[AbsolutePathBuf], windows_sandbox_workspace_roots: &[AbsolutePathBuf],
codex_linux_sandbox_exe: &Option<PathBuf>, codex_linux_sandbox_exe: &Option<PathBuf>,
codex_self_exe: &Option<PathBuf>,
use_legacy_landlock: bool, use_legacy_landlock: bool,
stdout_stream: Option<StdoutStream>, stdout_stream: Option<StdoutStream>,
) -> Result<ExecToolCallOutput> { ) -> Result<ExecToolCallOutput> {
@@ -309,6 +310,7 @@ pub async fn process_exec_tool_call(
sandbox_cwd, sandbox_cwd,
windows_sandbox_workspace_roots, windows_sandbox_workspace_roots,
codex_linux_sandbox_exe, codex_linux_sandbox_exe,
codex_self_exe,
use_legacy_landlock, use_legacy_landlock,
)?; )?;
@@ -324,6 +326,7 @@ pub fn build_exec_request(
sandbox_cwd: &AbsolutePathBuf, sandbox_cwd: &AbsolutePathBuf,
windows_sandbox_workspace_roots: &[AbsolutePathBuf], windows_sandbox_workspace_roots: &[AbsolutePathBuf],
codex_linux_sandbox_exe: &Option<PathBuf>, codex_linux_sandbox_exe: &Option<PathBuf>,
codex_self_exe: &Option<PathBuf>,
use_legacy_landlock: bool, use_legacy_landlock: bool,
) -> Result<ExecRequest> { ) -> Result<ExecRequest> {
let ExecParams { let ExecParams {
@@ -390,7 +393,11 @@ pub fn build_exec_request(
environment_id: network_environment_id.as_deref(), environment_id: network_environment_id.as_deref(),
network: network.as_ref(), network: network.as_ref(),
sandbox_policy_cwd: &sandbox_policy_cwd_uri, sandbox_policy_cwd: &sandbox_policy_cwd_uri,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_deref(), sandbox_exe: if cfg!(windows) {
codex_self_exe.as_deref()
} else {
codex_linux_sandbox_exe.as_deref()
},
use_legacy_landlock, use_legacy_landlock,
windows_sandbox_level, windows_sandbox_level,
windows_sandbox_private_desktop, windows_sandbox_private_desktop,
+4
View File
@@ -515,6 +515,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy(
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -1219,6 +1220,7 @@ fn build_exec_request_preserves_windows_workspace_roots() -> Result<()> {
&cwd, &cwd,
workspace_roots.as_slice(), workspace_roots.as_slice(),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
)?; )?;
@@ -1338,6 +1340,7 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
), ),
@@ -1419,6 +1422,7 @@ while :; do sleep 1; done"#
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
), ),
+6 -3
View File
@@ -77,9 +77,12 @@ fn permission_profile_sandbox_tag(
} }
} }
} }
if cfg!(target_os = "windows") && matches!(windows_sandbox_level, WindowsSandboxLevel::Elevated) if cfg!(target_os = "windows") {
{ match windows_sandbox_level {
return "windows_elevated"; WindowsSandboxLevel::Mxc => return "windows_mxc",
WindowsSandboxLevel::Elevated => return "windows_elevated",
WindowsSandboxLevel::Disabled | WindowsSandboxLevel::RestrictedToken => {}
}
} }
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
+4 -4
View File
@@ -39,7 +39,7 @@ pub(crate) struct ShellSnapshotSandbox {
enforce_managed_network: bool, enforce_managed_network: bool,
sandbox_policy_cwd: PathUri, sandbox_policy_cwd: PathUri,
workspace_roots: Vec<PathUri>, workspace_roots: Vec<PathUri>,
codex_linux_sandbox_exe: Option<PathBuf>, sandbox_exe: Option<PathBuf>,
use_legacy_landlock: bool, use_legacy_landlock: bool,
windows_sandbox_level: WindowsSandboxLevel, windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_private_desktop: bool, windows_sandbox_private_desktop: bool,
@@ -84,7 +84,7 @@ impl ShellSnapshotSandbox {
enforce_managed_network: attempt.enforce_managed_network, enforce_managed_network: attempt.enforce_managed_network,
sandbox_policy_cwd: attempt.sandbox_cwd.clone(), sandbox_policy_cwd: attempt.sandbox_cwd.clone(),
workspace_roots: attempt.workspace_roots.to_vec(), workspace_roots: attempt.workspace_roots.to_vec(),
codex_linux_sandbox_exe: attempt.codex_linux_sandbox_exe.cloned(), sandbox_exe: attempt.sandbox_exe.cloned(),
use_legacy_landlock: attempt.use_legacy_landlock, use_legacy_landlock: attempt.use_legacy_landlock,
windows_sandbox_level: attempt.windows_sandbox_level, windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop, windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
@@ -103,7 +103,7 @@ impl ShellSnapshotSandbox {
self.enforce_managed_network, self.enforce_managed_network,
&self.sandbox_policy_cwd, &self.sandbox_policy_cwd,
&self.workspace_roots, &self.workspace_roots,
&self.codex_linux_sandbox_exe, &self.sandbox_exe,
self.use_legacy_landlock, self.use_legacy_landlock,
self.windows_sandbox_level, self.windows_sandbox_level,
self.windows_sandbox_private_desktop, self.windows_sandbox_private_desktop,
@@ -172,7 +172,7 @@ impl ShellSnapshotSandbox {
environment_id: Some(&self.environment_id), environment_id: Some(&self.environment_id),
network: self.network.as_ref(), network: self.network.as_ref(),
sandbox_policy_cwd: &self.sandbox_policy_cwd, sandbox_policy_cwd: &self.sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(), sandbox_exe: self.sandbox_exe.as_deref(),
use_legacy_landlock: self.use_legacy_landlock, use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
@@ -42,7 +42,7 @@ async fn snapshot_failure_omits_credentials_and_stops_descendants(
manager: &manager, manager: &manager,
sandbox_cwd: &cwd_uri, sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri), workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
+1 -1
View File
@@ -416,7 +416,7 @@ async fn inactive_profiles_keep_snapshots_but_active_brokers_require_sandbox() -
manager: &manager, manager: &manager,
sandbox_cwd: &cwd_uri, sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri), workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
+10 -5
View File
@@ -88,7 +88,7 @@ impl ToolOrchestrator {
manager: attempt.manager, manager: attempt.manager,
sandbox_cwd: attempt.sandbox_cwd, sandbox_cwd: attempt.sandbox_cwd,
workspace_roots: attempt.workspace_roots, workspace_roots: attempt.workspace_roots,
codex_linux_sandbox_exe: attempt.codex_linux_sandbox_exe, sandbox_exe: attempt.sandbox_exe,
use_legacy_landlock: attempt.use_legacy_landlock, use_legacy_landlock: attempt.use_legacy_landlock,
windows_sandbox_level: attempt.windows_sandbox_level, windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop, windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
@@ -290,6 +290,11 @@ impl ToolOrchestrator {
.sandbox_cwd(req) .sandbox_cwd(req)
.cloned() .cloned()
.unwrap_or_else(|| environment.cwd().clone()); .unwrap_or_else(|| environment.cwd().clone());
let codex_sandbox_exe = if cfg!(windows) {
turn_ctx.config.codex_self_exe.as_ref()
} else {
turn_ctx.config.codex_linux_sandbox_exe.as_ref()
};
let initial_attempt = SandboxAttempt { let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox, sandbox: initial_sandbox,
sandbox_requested, sandbox_requested,
@@ -299,7 +304,7 @@ impl ToolOrchestrator {
manager: &sandbox_manager, manager: &sandbox_manager,
sandbox_cwd: &sandbox_policy_cwd, sandbox_cwd: &sandbox_policy_cwd,
workspace_roots, workspace_roots,
codex_linux_sandbox_exe: turn_ctx.config.codex_linux_sandbox_exe.as_ref(), sandbox_exe: codex_sandbox_exe,
use_legacy_landlock: sandbox_config.use_legacy_landlock, use_legacy_landlock: sandbox_config.use_legacy_landlock,
windows_sandbox_level: sandbox_config.windows_sandbox_level, windows_sandbox_level: sandbox_config.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop, windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop,
@@ -459,10 +464,10 @@ impl ToolOrchestrator {
} else { } else {
SandboxType::None SandboxType::None
}; };
let retry_codex_linux_sandbox_exe = if unsandboxed_allowed { let retry_sandbox_exe = if unsandboxed_allowed {
None None
} else { } else {
turn_ctx.config.codex_linux_sandbox_exe.as_ref() codex_sandbox_exe
}; };
let retry_attempt = SandboxAttempt { let retry_attempt = SandboxAttempt {
sandbox: retry_sandbox, sandbox: retry_sandbox,
@@ -473,7 +478,7 @@ impl ToolOrchestrator {
manager: &sandbox_manager, manager: &sandbox_manager,
sandbox_cwd: &sandbox_policy_cwd, sandbox_cwd: &sandbox_policy_cwd,
workspace_roots, workspace_roots,
codex_linux_sandbox_exe: retry_codex_linux_sandbox_exe, sandbox_exe: retry_sandbox_exe,
use_legacy_landlock: sandbox_config.use_legacy_landlock, use_legacy_landlock: sandbox_config.use_legacy_landlock,
windows_sandbox_level: sandbox_config.windows_sandbox_level, windows_sandbox_level: sandbox_config.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop, windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop,
@@ -246,7 +246,7 @@ async fn file_system_sandbox_context_preserves_executor_workspace_permissions()
manager: &manager, manager: &manager,
sandbox_cwd: &sandbox_policy_cwd, sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd), workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: true, use_legacy_landlock: true,
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken, windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
windows_sandbox_private_desktop: true, windows_sandbox_private_desktop: true,
@@ -315,7 +315,7 @@ async fn file_system_sandbox_context_respects_sandbox_request() {
manager: &manager, manager: &manager,
sandbox_cwd: &sandbox_policy_cwd, sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd), workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -124,7 +124,7 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
manager: &manager, manager: &manager,
sandbox_cwd: &sandbox_policy_cwd, sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd), workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -789,7 +789,7 @@ impl CoreShellCommandExecutor {
environment_id: self.network_environment_id.as_deref(), environment_id: self.network_environment_id.as_deref(),
network: self.network.as_ref(), network: self.network.as_ref(),
sandbox_policy_cwd: &sandbox_policy_cwd, sandbox_policy_cwd: &sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(), sandbox_exe: self.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: self.use_legacy_landlock, use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
+3 -5
View File
@@ -394,7 +394,7 @@ pub(crate) struct SandboxAttempt<'a> {
pub(crate) manager: &'a SandboxManager, pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a PathUri, pub(crate) sandbox_cwd: &'a PathUri,
pub(crate) workspace_roots: &'a [PathUri], pub(crate) workspace_roots: &'a [PathUri],
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>, pub sandbox_exe: Option<&'a std::path::PathBuf>,
// TODO(anp): Reconcile these attempt settings with TurnEnvironment::sandbox_context // TODO(anp): Reconcile these attempt settings with TurnEnvironment::sandbox_context
// so process execution and patch writes honor the selected environment's backend. // so process execution and patch writes honor the selected environment's backend.
pub use_legacy_landlock: bool, pub use_legacy_landlock: bool,
@@ -459,9 +459,7 @@ impl<'a> SandboxAttempt<'a> {
environment_id, environment_id,
network, network,
sandbox_policy_cwd: self.sandbox_cwd, sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: self sandbox_exe: self.sandbox_exe.map(std::path::PathBuf::as_path),
.codex_linux_sandbox_exe
.map(std::path::PathBuf::as_path),
use_legacy_landlock: self.use_legacy_landlock, use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
@@ -496,7 +494,7 @@ impl<'a> SandboxAttempt<'a> {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: self.sandbox_cwd, sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: self.use_legacy_landlock, use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
+2 -2
View File
@@ -255,7 +255,7 @@ fn windows_sandbox_env_preserves_denied_reads_or_rejects_unsupported_backend() {
manager: &manager, manager: &manager,
sandbox_cwd: &cwd_uri, sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri), workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Elevated, windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -326,7 +326,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
manager: &manager, manager: &manager,
sandbox_cwd: &cwd_uri, sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri), workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled, windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
+1
View File
@@ -55,6 +55,7 @@ where
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -375,6 +375,7 @@ async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> a
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -424,6 +425,7 @@ async fn windows_elevated_does_not_create_missing_workspace_metadata() -> anyhow
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -603,6 +605,7 @@ async fn windows_elevated_enforces_deny_read_and_protects_setup_marker() -> anyh
&cwd, &cwd,
std::slice::from_ref(&cwd), std::slice::from_ref(&cwd),
&None, &None,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -152,6 +152,9 @@ pub struct EnvironmentCapabilities {
/// Whether shell state can be cached and restored entirely inside the executor. /// Whether shell state can be cached and restored entirely inside the executor.
#[serde(default)] #[serde(default)]
pub shell_snapshot_v2: bool, pub shell_snapshot_v2: bool,
/// Whether requests may explicitly select the MXC Windows sandbox backend.
#[serde(default)]
pub windows_mxc: bool,
} }
/// Status returned by an initialized exec-server connection. /// Status returned by an initialized exec-server connection.
@@ -243,6 +246,7 @@ impl EnvironmentInfo {
http_header_env_vars: true, http_header_env_vars: true,
sandboxed_file_streaming: true, sandboxed_file_streaming: true,
shell_snapshot_v2: cfg!(unix), shell_snapshot_v2: cfg!(unix),
windows_mxc: false,
}, },
} }
} }
@@ -358,6 +362,7 @@ pub enum ProcessSandboxType {
MacosSeatbelt, MacosSeatbelt,
LinuxSeccomp, LinuxSeccomp,
WindowsRestrictedToken, WindowsRestrictedToken,
WindowsMxc,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -1057,6 +1062,7 @@ mod tests {
http_header_env_vars: false, http_header_env_vars: false,
sandboxed_file_streaming: false, sandboxed_file_streaming: false,
shell_snapshot_v2: false, shell_snapshot_v2: false,
windows_mxc: false,
} }
); );
} }
@@ -1078,6 +1084,7 @@ mod tests {
"httpHeaderEnvVars": false, "httpHeaderEnvVars": false,
"sandboxedFileStreaming": false, "sandboxedFileStreaming": false,
"shellSnapshotV2": false, "shellSnapshotV2": false,
"windowsMxc": false,
}, },
}); });
let info: EnvironmentInfo = serde_json::from_value(expected.clone()) let info: EnvironmentInfo = serde_json::from_value(expected.clone())
+5 -1
View File
@@ -176,7 +176,11 @@ impl FileSystemSandboxRunner {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd.uri, sandbox_policy_cwd: &cwd.uri,
codex_linux_sandbox_exe: self.runtime_paths.codex_linux_sandbox_exe.as_deref(), sandbox_exe: if cfg!(windows) {
Some(self.runtime_paths.codex_self_exe.as_path())
} else {
self.runtime_paths.codex_linux_sandbox_exe.as_deref()
},
use_legacy_landlock: sandbox_context.use_legacy_landlock, use_legacy_landlock: sandbox_context.use_legacy_landlock,
windows_sandbox_level: sandbox_context.windows_sandbox_level, windows_sandbox_level: sandbox_context.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_context windows_sandbox_private_desktop: sandbox_context
@@ -396,6 +396,7 @@ impl LocalProcess {
SandboxType::MacosSeatbelt => Some(ProcessSandboxType::MacosSeatbelt), SandboxType::MacosSeatbelt => Some(ProcessSandboxType::MacosSeatbelt),
SandboxType::LinuxSeccomp => Some(ProcessSandboxType::LinuxSeccomp), SandboxType::LinuxSeccomp => Some(ProcessSandboxType::LinuxSeccomp),
SandboxType::WindowsRestrictedToken => Some(ProcessSandboxType::WindowsRestrictedToken), SandboxType::WindowsRestrictedToken => Some(ProcessSandboxType::WindowsRestrictedToken),
SandboxType::WindowsMxc => Some(ProcessSandboxType::WindowsMxc),
}; };
let start = Arc::new(ProcessStart); let start = Arc::new(ProcessStart);
+1
View File
@@ -35,6 +35,7 @@ pub(crate) fn sandbox_type_from_protocol(
Some(ProcessSandboxType::WindowsRestrictedToken) => { Some(ProcessSandboxType::WindowsRestrictedToken) => {
Some(SandboxType::WindowsRestrictedToken) Some(SandboxType::WindowsRestrictedToken)
} }
Some(ProcessSandboxType::WindowsMxc) => Some(SandboxType::WindowsMxc),
} }
} }
+27 -1
View File
@@ -84,6 +84,28 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
network_policy_audit_observer: Option<NetworkPolicyAuditObserver>, network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
telemetry: &ProcessTelemetry, telemetry: &ProcessTelemetry,
) -> Result<PreparedExecRequest, JSONRPCErrorError> { ) -> Result<PreparedExecRequest, JSONRPCErrorError> {
if let Some(sandbox) = params.sandbox.as_ref()
&& sandbox.windows_sandbox_level == codex_protocol::config_types::WindowsSandboxLevel::Mxc
{
if params.tty || params.arg0.is_some() {
return Err(invalid_params(
"MXC currently supports ordinary pipe launches only".to_owned(),
));
}
if params.enforce_managed_network
|| params.managed_network.is_some()
|| params.network_proxy.is_some()
{
return Err(invalid_params(
"MXC managed networking is not supported yet".to_owned(),
));
}
if !codex_sandboxing::windows_mxc_available() {
return Err(invalid_params(
"native MXC is unavailable on this executor".to_owned(),
));
}
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let mut env = env; let mut env = env;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -241,7 +263,11 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd, sandbox_policy_cwd,
codex_linux_sandbox_exe: runtime_paths.codex_linux_sandbox_exe.as_deref(), sandbox_exe: if cfg!(windows) {
Some(runtime_paths.codex_self_exe.as_path())
} else {
runtime_paths.codex_linux_sandbox_exe.as_deref()
},
use_legacy_landlock: sandbox_context.use_legacy_landlock, use_legacy_landlock: sandbox_context.use_legacy_landlock,
windows_sandbox_level: sandbox_context.windows_sandbox_level, windows_sandbox_level: sandbox_context.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop, windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
@@ -129,6 +129,33 @@ async fn sandbox_request_wraps_native_argv_on_executor() {
prepared.command.first().map(String::as_str), prepared.command.first().map(String::as_str),
Some("/usr/bin/sandbox-exec") Some("/usr/bin/sandbox-exec")
); );
let mut params = params;
params.sandbox.as_mut().unwrap().windows_sandbox_level =
codex_protocol::config_types::WindowsSandboxLevel::Mxc;
for (tty, managed, message) in [
(false, false, "native MXC is unavailable on this executor"),
(
true,
false,
"MXC currently supports ordinary pipe launches only",
),
(false, true, "MXC managed networking is not supported yet"),
] {
params.tty = tty;
params.enforce_managed_network = managed;
let error = prepare_exec_request(
&params,
HashMap::new(),
Some(&runtime_paths),
/*network_policy_decider*/ None,
/*network_policy_audit_observer*/ None,
)
.await
.err()
.expect("unsupported MXC must fail closed");
assert_eq!(error.message, message);
}
} }
#[cfg(unix)] #[cfg(unix)]
+3
View File
@@ -44,6 +44,9 @@ pub static TEST_BINARY_DISPATCH_GUARD: Option<TestBinaryDispatchGuard> = {
if argv1 == Some(CODEX_WINDOWS_SANDBOX_ARG1) { if argv1 == Some(CODEX_WINDOWS_SANDBOX_ARG1) {
return TestBinaryDispatchMode::DispatchArg0Only; return TestBinaryDispatchMode::DispatchArg0Only;
} }
if argv1 == Some(codex_sandboxing::CODEX_WINDOWS_MXC_ARG1) {
return TestBinaryDispatchMode::DispatchArg0Only;
}
if exe_name == CODEX_LINUX_SANDBOX_ARG0 { if exe_name == CODEX_LINUX_SANDBOX_ARG0 {
return TestBinaryDispatchMode::DispatchArg0Only; return TestBinaryDispatchMode::DispatchArg0Only;
} }
+40 -6
View File
@@ -1,4 +1,6 @@
mod common; mod common;
#[path = "exec_process/windows_sandbox.rs"]
mod windows_sandbox;
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(unix)] #[cfg(unix)]
@@ -102,6 +104,10 @@ async fn create_process_context(use_remote: bool) -> Result<ProcessContext> {
} }
} }
fn selected_windows_sandbox_available(windows_sandbox_level: WindowsSandboxLevel) -> bool {
windows_sandbox_level != WindowsSandboxLevel::Mxc || codex_sandboxing::windows_mxc_available()
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn codex_home_symlink_opt_out_respects_host_config_and_scope() -> Result<()> { async fn codex_home_symlink_opt_out_respects_host_config_and_scope() -> Result<()> {
@@ -914,13 +920,20 @@ async fn collect_process_output_from_reads(
async fn collect_process_output_from_events( async fn collect_process_output_from_events(
session: Arc<dyn ExecProcess>, session: Arc<dyn ExecProcess>,
) -> Result<(String, String, Option<i32>, bool)> {
collect_process_output_from_events_with_timeout(session, Duration::from_secs(2)).await
}
async fn collect_process_output_from_events_with_timeout(
session: Arc<dyn ExecProcess>,
event_timeout: Duration,
) -> Result<(String, String, Option<i32>, bool)> { ) -> Result<(String, String, Option<i32>, bool)> {
let mut events = session.subscribe_events(); let mut events = session.subscribe_events();
let mut stdout = String::new(); let mut stdout = String::new();
let mut stderr = String::new(); let mut stderr = String::new();
let mut exit_code = None; let mut exit_code = None;
loop { loop {
match timeout(Duration::from_secs(2), events.recv()).await?? { match timeout(event_timeout, events.recv()).await?? {
ExecProcessEvent::Output(chunk) => match chunk.stream { ExecProcessEvent::Output(chunk) => match chunk.stream {
ExecOutputStream::Stdout | ExecOutputStream::Pty => { ExecOutputStream::Stdout | ExecOutputStream::Pty => {
stdout.push_str(&String::from_utf8_lossy(&chunk.chunk.into_inner())); stdout.push_str(&String::from_utf8_lossy(&chunk.chunk.into_inner()));
@@ -1281,7 +1294,14 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
Ok(()) Ok(())
} }
async fn assert_remote_windows_sandbox_process_write() -> Result<()> { async fn assert_remote_windows_sandbox_process_write(
windows_sandbox_level: WindowsSandboxLevel,
expected_sandbox_type: codex_sandboxing::SandboxType,
) -> Result<()> {
if !selected_windows_sandbox_available(windows_sandbox_level) {
eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host");
return Ok(());
}
let context = create_process_context(/*use_remote*/ true).await?; let context = create_process_context(/*use_remote*/ true).await?;
let workspace = TempDir::new()?; let workspace = TempDir::new()?;
let blocked_file = workspace.path().join("blocked.txt"); let blocked_file = workspace.path().join("blocked.txt");
@@ -1290,7 +1310,7 @@ async fn assert_remote_windows_sandbox_process_write() -> Result<()> {
SandboxPolicy::new_read_only_policy(), SandboxPolicy::new_read_only_policy(),
cwd.clone(), cwd.clone(),
)?; )?;
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; sandbox.windows_sandbox_level = windows_sandbox_level;
let session = match context let session = match context
.backend .backend
@@ -1325,6 +1345,7 @@ async fn assert_remote_windows_sandbox_process_write() -> Result<()> {
Ok(session) => session, Ok(session) => session,
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
assert_eq!(session.sandbox_type, Some(expected_sandbox_type));
let write_response = session.process.write(b"hello\n".to_vec()).await?; let write_response = session.process.write(b"hello\n".to_vec()).await?;
assert_eq!(write_response.status, WriteStatus::Accepted); assert_eq!(write_response.status, WriteStatus::Accepted);
@@ -1743,11 +1764,24 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
assert_exec_process_write_then_read_without_tty(use_remote).await assert_exec_process_write_then_read_without_tty(use_remote).await
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[test_case(
WindowsSandboxLevel::RestrictedToken,
codex_sandboxing::SandboxType::WindowsRestrictedToken;
"restricted_token"
)]
#[test_case(
WindowsSandboxLevel::Mxc,
codex_sandboxing::SandboxType::WindowsMxc;
"mxc"
)]
#[cfg_attr(not(windows), ignore = "Windows-only exec-server sandbox process test")] #[cfg_attr(not(windows), ignore = "Windows-only exec-server sandbox process test")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[serial_test::serial(remote_exec_server)] #[serial_test::serial(remote_exec_server)]
async fn remote_windows_sandbox_process_accepts_process_write() -> Result<()> { async fn remote_windows_sandbox_process_accepts_process_write(
assert_remote_windows_sandbox_process_write().await windows_sandbox_level: WindowsSandboxLevel,
expected_sandbox_type: codex_sandboxing::SandboxType,
) -> Result<()> {
assert_remote_windows_sandbox_process_write(windows_sandbox_level, expected_sandbox_type).await
} }
#[test_case(false ; "local")] #[test_case(false ; "local")]
@@ -0,0 +1,112 @@
//! Shared Windows sandbox behavior over the real exec-server RPC connection.
use super::*;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use pretty_assertions::assert_eq;
#[cfg_attr(not(windows), ignore = "requires a native Windows sandbox")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[serial_test::serial(remote_exec_server)]
async fn mxc_tmpdir_uses_command_environment_over_rpc() -> Result<()> {
if !selected_windows_sandbox_available(WindowsSandboxLevel::Mxc) {
eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host");
return Ok(());
}
let root = TempDir::new()?;
let command_temp = root.path().join("command temp");
let server_temp = root.path().join("server temp");
std::fs::create_dir(&command_temp)?;
std::fs::create_dir(&server_temp)?;
let outside = server_temp.join("outside.txt");
std::fs::write(&outside, "original")?;
let server = common::exec_server::exec_server_with_env(
[
("TEMP", server_temp.as_os_str()),
("TMP", server_temp.as_os_str()),
],
&[],
)
.await?;
let environment = Environment::create_for_tests(Some(server.websocket_url().to_owned()))?;
let cwd = PathUri::from_host_native_path(root.path())?;
let fs = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
),
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Tmpdir,
},
FileSystemAccessMode::Write,
),
]);
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::from_runtime_permissions(&fs, NetworkSandboxPolicy::Restricted),
cwd.clone(),
);
sandbox.windows_sandbox_level = WindowsSandboxLevel::Mxc;
let command_temp = command_temp.to_string_lossy().into_owned();
let started = environment
.get_exec_backend()
.start(ExecParams {
process_id: ProcessId::from("windows-sandbox-temp"),
metadata: None,
argv: vec![
r"C:\Windows\System32\cmd.exe".to_owned(),
"/D".to_owned(),
"/S".to_owned(),
"/C".to_owned(),
format!(
"echo allowed>\"%TEMP%\\allowed.txt\" & 2>\"%TEMP%\\denied.txt\" echo modified>\"{}\" & exit /b 0",
outside.display()
),
],
cwd,
env_policy: None,
shell_snapshot: None,
env: HashMap::from([
("SystemRoot".to_owned(), std::env::var("SystemRoot")?),
("TEMP".to_owned(), command_temp.clone()),
("TMP".to_owned(), command_temp),
]),
tty: false,
pipe_stdin: false,
arg0: None,
sandbox: Some(sandbox),
enforce_managed_network: false,
managed_network: None,
network_proxy: None,
})
.await?;
assert_eq!(
started.sandbox_type,
Some(codex_sandboxing::SandboxType::WindowsMxc)
);
assert_eq!(
collect_process_output_from_events_with_timeout(
started.process,
Duration::from_secs(/*secs*/ 30),
)
.await?,
(String::new(), String::new(), Some(0), true)
);
assert_eq!(
(
std::fs::read_to_string(root.path().join("command temp").join("allowed.txt"))?
.trim_end()
.to_owned(),
std::fs::read_to_string(outside)?
),
("allowed".to_owned(), "original".to_owned())
);
Ok(())
}
+1
View File
@@ -52,6 +52,7 @@ pub(super) async fn spawn_command_under_sandbox(
sandbox_cwd, sandbox_cwd,
std::slice::from_ref(sandbox_cwd), std::slice::from_ref(sandbox_cwd),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
) )
.map_err(|err| io::Error::other(err.to_string()))?; .map_err(|err| io::Error::other(err.to_string()))?;
@@ -195,6 +195,7 @@ async fn run_cmd_result_with_permission_profile_for_cwd(
&sandbox_cwd, &sandbox_cwd,
std::slice::from_ref(&sandbox_cwd), std::slice::from_ref(&sandbox_cwd),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
/*codex_self_exe*/ &None,
use_legacy_landlock, use_legacy_landlock,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
@@ -891,6 +892,7 @@ async fn assert_network_blocked(cmd: &[&str]) {
&sandbox_cwd, &sandbox_cwd,
std::slice::from_ref(&sandbox_cwd), std::slice::from_ref(&sandbox_cwd),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
/*codex_self_exe*/ &None,
/*use_legacy_landlock*/ false, /*use_legacy_landlock*/ false,
/*stdout_stream*/ None, /*stdout_stream*/ None,
) )
+2 -3
View File
@@ -7,9 +7,8 @@ dispatcher, edits host ACLs, creates sandbox users, runs setup, or requests
elevation. The existing Codex Windows sandboxes remain separate backends. elevation. The existing Codex Windows sandboxes remain separate backends.
Windows executors record `codex.windows_mxc.available` once per process with an Windows executors record `codex.windows_mxc.available` once per process with an
`available=true|false` tag. This measures capability independently of selection. `available=true|false` tag. This measures runtime availability independently of
The exec-server handshake also exposes `windows_mxc_available`; older or selection. Unsupported Windows executors reject MXC requests before execution.
unsupported Windows executors reject MXC requests before execution.
`is_available()` uses MXC's cached create/close probe, rather than an OS build `is_available()` uses MXC's cached create/close probe, rather than an OS build
number or the SDK's broad `platform_support()` result. The latter also reports number or the SDK's broad `platform_support()` result. The latter also reports
+1 -1
View File
@@ -106,7 +106,7 @@ pub fn run_main() -> ! {
match windows::run() { match windows::run() {
Ok(exit_code) => std::process::exit(exit_code), Ok(exit_code) => std::process::exit(exit_code),
Err(error) => { Err(error) => {
eprintln!("MXC sandbox: {error:#}"); eprintln!("MXC launcher: {error:#}");
std::process::exit(1); std::process::exit(1);
} }
} }
+5 -5
View File
@@ -43,11 +43,11 @@ pub(super) fn run() -> Result<i32> {
Ok((key, value)) Ok((key, value))
}) })
.collect::<Result<HashMap<_, _>>>()?; .collect::<Result<HashMap<_, _>>>()?;
let command = crate::transport::decode(&mut env)?; // Serde errors can quote arbitrary payload values; do not print them to stderr.
ensure!( let command = crate::transport::decode(&mut env)
!env.is_empty(), .map_err(|_| anyhow::anyhow!("invalid MXC launcher request"))?;
"MXC requires an explicit child environment" // The transport itself proves this is an explicit child environment; it
); // may intentionally contain no variables once launcher state is removed.
let mask = unsafe { GetLogicalDrives() }; let mask = unsafe { GetLogicalDrives() };
ensure!( ensure!(
mask != 0, mask != 0,
+1
View File
@@ -299,6 +299,7 @@ pub enum WindowsSandboxLevel {
Disabled, Disabled,
RestrictedToken, RestrictedToken,
Elevated, Elevated,
Mxc,
} }
/// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves /// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves
+4
View File
@@ -18,6 +18,7 @@ pub use bwrap::find_system_bwrap_in_path;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub use bwrap::system_bwrap_warning; pub use bwrap::system_bwrap_warning;
pub use codex_mxc_sandbox::CODEX_WINDOWS_MXC_ARG1; pub use codex_mxc_sandbox::CODEX_WINDOWS_MXC_ARG1;
pub use codex_mxc_sandbox::is_available as windows_mxc_available;
pub use codex_mxc_sandbox::run_main as run_windows_mxc_main; pub use codex_mxc_sandbox::run_main as run_windows_mxc_main;
pub use codex_windows_sandbox::WindowsSandboxProxySettingsMode; pub use codex_windows_sandbox::WindowsSandboxProxySettingsMode;
pub use denial::is_likely_executor_managed_sandbox_denied; pub use denial::is_likely_executor_managed_sandbox_denied;
@@ -73,6 +74,9 @@ impl From<SandboxTransformError> for CodexErr {
SandboxTransformError::EnvironmentNetworkProxy(message) => { SandboxTransformError::EnvironmentNetworkProxy(message) => {
CodexErr::UnsupportedOperation(message) CodexErr::UnsupportedOperation(message)
} }
SandboxTransformError::WindowsMxcPreparation(message) => {
CodexErr::UnsupportedOperation(message)
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
SandboxTransformError::SeatbeltPreparation(message) => { SandboxTransformError::SeatbeltPreparation(message) => {
CodexErr::UnsupportedOperation(message) CodexErr::UnsupportedOperation(message)
+79 -33
View File
@@ -44,6 +44,7 @@ pub enum SandboxType {
MacosSeatbelt, MacosSeatbelt,
LinuxSeccomp, LinuxSeccomp,
WindowsRestrictedToken, WindowsRestrictedToken,
WindowsMxc,
} }
impl SandboxType { impl SandboxType {
@@ -53,6 +54,7 @@ impl SandboxType {
SandboxType::MacosSeatbelt => "seatbelt", SandboxType::MacosSeatbelt => "seatbelt",
SandboxType::LinuxSeccomp => "seccomp", SandboxType::LinuxSeccomp => "seccomp",
SandboxType::WindowsRestrictedToken => "windows_sandbox", SandboxType::WindowsRestrictedToken => "windows_sandbox",
SandboxType::WindowsMxc => "windows_mxc",
} }
} }
} }
@@ -145,7 +147,7 @@ pub struct SandboxTransformRequest<'a> {
// to make shared ownership explicit across runtime/sandbox plumbing. // to make shared ownership explicit across runtime/sandbox plumbing.
pub network: Option<&'a NetworkProxy>, pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a PathUri, pub sandbox_policy_cwd: &'a PathUri,
pub codex_linux_sandbox_exe: Option<&'a Path>, pub sandbox_exe: Option<&'a Path>,
// TODO(anp): Reconcile these backend inputs with the supplied sandbox context // TODO(anp): Reconcile these backend inputs with the supplied sandbox context
// (TurnEnvironment::sandbox_context for turns) so selection shares its authority. // (TurnEnvironment::sandbox_context for turns) so selection shares its authority.
pub use_legacy_landlock: bool, pub use_legacy_landlock: bool,
@@ -215,6 +217,7 @@ pub enum SandboxTransformError {
source: io::Error, source: io::Error,
}, },
MissingLinuxSandboxExecutable, MissingLinuxSandboxExecutable,
WindowsMxcPreparation(String),
EnvironmentNetworkProxy(String), EnvironmentNetworkProxy(String),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
SeatbeltPreparation(String), SeatbeltPreparation(String),
@@ -242,6 +245,9 @@ impl std::fmt::Display for SandboxTransformError {
Self::MissingLinuxSandboxExecutable => { Self::MissingLinuxSandboxExecutable => {
write!(f, "missing codex-linux-sandbox executable path") write!(f, "missing codex-linux-sandbox executable path")
} }
Self::WindowsMxcPreparation(err) => {
write!(f, "failed to prepare MXC sandbox: {err}")
}
Self::EnvironmentNetworkProxy(err) => { Self::EnvironmentNetworkProxy(err) => {
write!(f, "failed to prepare environment network proxy: {err}") write!(f, "failed to prepare environment network proxy: {err}")
} }
@@ -267,6 +273,7 @@ impl std::error::Error for SandboxTransformError {
Self::InvalidCommandCwd { source, .. } Self::InvalidCommandCwd { source, .. }
| Self::InvalidSandboxPolicyCwd { source, .. } => Some(source), | Self::InvalidSandboxPolicyCwd { source, .. } => Some(source),
Self::MissingLinuxSandboxExecutable => None, Self::MissingLinuxSandboxExecutable => None,
Self::WindowsMxcPreparation(_) => None,
Self::EnvironmentNetworkProxy(_) => None, Self::EnvironmentNetworkProxy(_) => None,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
Self::SeatbeltPreparation(_) => None, Self::SeatbeltPreparation(_) => None,
@@ -324,12 +331,14 @@ impl SandboxManager {
#[cfg(windows)] #[cfg(windows)]
crate::windows_mxc::record_availability_once(); crate::windows_mxc::record_availability_once();
if self.should_sandbox(permission_profile, pref, has_managed_network_requirements) { if !self.should_sandbox(permission_profile, pref, has_managed_network_requirements) {
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) return SandboxType::None;
.unwrap_or(SandboxType::None)
} else {
SandboxType::None
} }
if cfg!(windows) && windows_sandbox_level == WindowsSandboxLevel::Mxc {
return SandboxType::WindowsMxc;
}
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
.unwrap_or(SandboxType::None)
} }
/// Returns whether the request needs a sandbox, independently of whether /// Returns whether the request needs a sandbox, independently of whether
@@ -367,7 +376,7 @@ impl SandboxManager {
environment_id, environment_id,
network, network,
sandbox_policy_cwd, sandbox_policy_cwd,
codex_linux_sandbox_exe, sandbox_exe,
use_legacy_landlock, use_legacy_landlock,
windows_sandbox_level, windows_sandbox_level,
windows_sandbox_private_desktop, windows_sandbox_private_desktop,
@@ -386,11 +395,62 @@ impl SandboxManager {
managed_mitm_ca_trust_bundle_path.as_ref(), managed_mitm_ca_trust_bundle_path.as_ref(),
); );
let mut argv = Vec::with_capacity(1 + command.args.len()); let mut argv = Vec::with_capacity(1 + command.args.len());
argv.push(command.program); argv.push(os_string_to_command_component(command.program));
argv.extend(command.args.into_iter().map(OsString::from)); argv.extend(command.args);
let (argv, arg0_override, pending_sandboxed_request) = match sandbox { let (argv, arg0_override, pending_sandboxed_request) = match sandbox {
SandboxType::None => (os_argv_to_strings(argv), None, None), SandboxType::None => (argv, None, None),
SandboxType::WindowsMxc => {
if windows_sandbox_private_desktop {
return Err(SandboxTransformError::WindowsMxcPreparation(
"private desktop isolation is not supported by MXC".to_string(),
));
}
if !codex_mxc_sandbox::is_available() {
return Err(SandboxTransformError::WindowsMxcPreparation(
"native MXC is unavailable on this executor".to_string(),
));
}
if enforce_managed_network && command.managed_network.is_none() {
let network = network.ok_or_else(|| {
SandboxTransformError::WindowsMxcPreparation(
"managed networking requires an executor-local proxy".to_string(),
)
})?;
let prepared = network
.prepare_for_optional_environment(
std::mem::take(&mut command.env),
environment_id,
)
.map_err(|err| {
SandboxTransformError::EnvironmentNetworkProxy(err.to_string())
})?;
command.env = prepared.env;
command.managed_network = Some(prepared.sandbox_context);
}
let managed_network = command.managed_network.filter(|_| enforce_managed_network);
let pending = pending_sandboxed_request?;
let exe = sandbox_exe.ok_or_else(|| {
SandboxTransformError::WindowsMxcPreparation(
"missing Codex executable path".to_string(),
)
})?;
let mut full_command =
vec![os_string_to_command_component(exe.as_os_str().to_owned())];
full_command.extend(
codex_mxc_sandbox::create_command_args(
codex_mxc_sandbox::CreateMxcCommandArgsParams {
command: argv,
permission_profile: &pending.effective_permission_profile,
sandbox_policy_cwd: pending.native_sandbox_policy_cwd.as_path(),
managed_network: managed_network.as_ref(),
env: &mut command.env,
},
)
.map_err(|err| SandboxTransformError::WindowsMxcPreparation(err.to_string()))?,
);
(full_command, None, Some(pending))
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => { SandboxType::MacosSeatbelt => {
use crate::seatbelt::CreateSeatbeltCommandArgsParams; use crate::seatbelt::CreateSeatbeltCommandArgsParams;
@@ -404,7 +464,7 @@ impl SandboxManager {
.to_runtime_permissions(); .to_runtime_permissions();
let mut args = create_seatbelt_command_args_with_profile( let mut args = create_seatbelt_command_args_with_profile(
CreateSeatbeltCommandArgsParams { CreateSeatbeltCommandArgsParams {
command: os_argv_to_strings(argv), command: argv,
file_system_sandbox_policy: &file_system_sandbox_policy, file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy, network_sandbox_policy,
sandbox_policy_cwd: pending.native_sandbox_policy_cwd.as_path(), sandbox_policy_cwd: pending.native_sandbox_policy_cwd.as_path(),
@@ -434,8 +494,8 @@ impl SandboxManager {
SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable), SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable),
SandboxType::LinuxSeccomp => { SandboxType::LinuxSeccomp => {
let pending = pending_sandboxed_request?; let pending = pending_sandboxed_request?;
let exe = codex_linux_sandbox_exe let exe =
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?; sandbox_exe.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
let allow_proxy_network = allow_network_for_proxy(enforce_managed_network); let allow_proxy_network = allow_network_for_proxy(enforce_managed_network);
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
ensure_linux_bubblewrap_is_supported( ensure_linux_bubblewrap_is_supported(
@@ -447,7 +507,7 @@ impl SandboxManager {
is_wsl1(), is_wsl1(),
)?; )?;
let mut args = create_linux_sandbox_command_args_for_permission_profile( let mut args = create_linux_sandbox_command_args_for_permission_profile(
os_argv_to_strings(argv), argv,
pending.native_command_cwd.as_path(), pending.native_command_cwd.as_path(),
&pending.effective_permission_profile, &pending.effective_permission_profile,
pending.native_sandbox_policy_cwd.as_path(), pending.native_sandbox_policy_cwd.as_path(),
@@ -487,14 +547,10 @@ impl SandboxManager {
)], )],
); );
} }
(os_argv_to_strings(argv), None, Some(pending)) (argv, None, Some(pending))
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
SandboxType::WindowsRestrictedToken => ( SandboxType::WindowsRestrictedToken => (argv, None, Some(pending_sandboxed_request?)),
os_argv_to_strings(argv),
None,
Some(pending_sandboxed_request?),
),
}; };
// Unsandboxed exec-server requests may have foreign cwd values that cannot be prepared // Unsandboxed exec-server requests may have foreign cwd values that cannot be prepared
@@ -525,16 +581,12 @@ impl SandboxManager {
request: SandboxDirectSpawnTransformRequest<'_>, request: SandboxDirectSpawnTransformRequest<'_>,
) -> Result<SandboxExecRequest, SandboxTransformError> { ) -> Result<SandboxExecRequest, SandboxTransformError> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ if request.transform.sandbox == SandboxType::WindowsRestrictedToken {
let codex_home = codex_utils_home_dir::find_codex_home() let codex_home = codex_utils_home_dir::find_codex_home()
.map_err(|err| SandboxTransformError::WindowsSandboxPreparation(err.to_string()))?; .map_err(|err| SandboxTransformError::WindowsSandboxPreparation(err.to_string()))?;
self.transform_for_direct_spawn_with_codex_home(request, codex_home.as_path()) return self.transform_for_direct_spawn_with_codex_home(request, codex_home.as_path());
}
#[cfg(not(target_os = "windows"))]
{
self.transform(request.transform)
} }
self.transform(request.transform)
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -751,12 +803,6 @@ fn ensure_linux_bubblewrap_is_supported(
Ok(()) Ok(())
} }
fn os_argv_to_strings(argv: Vec<OsString>) -> Vec<String> {
argv.into_iter()
.map(os_string_to_command_component)
.collect()
}
fn os_string_to_command_component(value: OsString) -> String { fn os_string_to_command_component(value: OsString) -> String {
value value
.into_string() .into_string()
+48 -6
View File
@@ -25,6 +25,48 @@ use pretty_assertions::assert_eq;
use std::collections::HashMap; use std::collections::HashMap;
use tempfile::TempDir; use tempfile::TempDir;
#[test]
fn mxc_selection_preserves_unsandboxed_requests_and_other_platforms() {
let manager = SandboxManager::new();
let platform_sandbox = if cfg!(windows) {
SandboxType::WindowsMxc
} else {
get_platform_sandbox(/*windows_sandbox_enabled*/ false).unwrap_or(SandboxType::None)
};
for (permissions, preference, expected) in [
(
PermissionProfile::read_only(),
SandboxablePreference::Auto,
platform_sandbox,
),
(
PermissionProfile::Disabled,
SandboxablePreference::Require,
platform_sandbox,
),
(
PermissionProfile::read_only(),
SandboxablePreference::Forbid,
SandboxType::None,
),
(
PermissionProfile::Disabled,
SandboxablePreference::Auto,
SandboxType::None,
),
] {
assert_eq!(
manager.select_initial(
&permissions,
preference,
WindowsSandboxLevel::Mxc,
/*has_managed_network_requirements*/ false,
),
expected,
);
}
}
#[test] #[test]
fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() { fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
let manager = SandboxManager::new(); let manager = SandboxManager::new();
@@ -103,7 +145,7 @@ fn unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_poli
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd_uri, sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -160,7 +202,7 @@ fn symlinked_workspace_reports_seatbelt_preparation_error() {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &workspace_uri, sandbox_policy_cwd: &workspace_uri,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -215,7 +257,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd_uri, sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -286,7 +328,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd_uri, sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -388,7 +430,7 @@ fn transform_linux_seccomp_request(
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd_uri, sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: Some(codex_linux_sandbox_exe), sandbox_exe: Some(codex_linux_sandbox_exe),
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
@@ -590,7 +632,7 @@ fn transform_for_direct_spawn_windows_materializes_inner_helper() {
environment_id: None, environment_id: None,
network: None, network: None,
sandbox_policy_cwd: &cwd_uri, sandbox_policy_cwd: &cwd_uri,
codex_linux_sandbox_exe: None, sandbox_exe: None,
use_legacy_landlock: false, use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Elevated, windows_sandbox_level: WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
+3
View File
@@ -50,6 +50,7 @@ pub enum SandboxViolationBackend {
ManagedNetworkProxy, ManagedNetworkProxy,
Seatbelt, Seatbelt,
WindowsSandbox, WindowsSandbox,
WindowsMxc,
} }
impl SandboxViolationBackend { impl SandboxViolationBackend {
@@ -59,6 +60,7 @@ impl SandboxViolationBackend {
Self::ManagedNetworkProxy => "managed_network_proxy", Self::ManagedNetworkProxy => "managed_network_proxy",
Self::Seatbelt => "seatbelt", Self::Seatbelt => "seatbelt",
Self::WindowsSandbox => "windows_sandbox", Self::WindowsSandbox => "windows_sandbox",
Self::WindowsMxc => "windows_mxc",
} }
} }
} }
@@ -143,6 +145,7 @@ fn classify_filesystem_sandbox_violation(
SandboxType::MacosSeatbelt => SandboxViolationBackend::Seatbelt, SandboxType::MacosSeatbelt => SandboxViolationBackend::Seatbelt,
SandboxType::LinuxSeccomp => SandboxViolationBackend::LinuxSandbox, SandboxType::LinuxSeccomp => SandboxViolationBackend::LinuxSandbox,
SandboxType::WindowsRestrictedToken => SandboxViolationBackend::WindowsSandbox, SandboxType::WindowsRestrictedToken => SandboxViolationBackend::WindowsSandbox,
SandboxType::WindowsMxc => SandboxViolationBackend::WindowsMxc,
}; };
if let Some((reason, output)) = filesystem_reason_from_output(exec_output) { if let Some((reason, output)) = filesystem_reason_from_output(exec_output) {