Enable MXC selection through Windows sandbox configuration (#46271)

## What changed

- Accept `windows.sandbox = "mxc"` and preserve the selected backend through environment configuration, command execution, patch writes, and sandbox metadata.
- Treat MXC as enabled in the TUI and report Windows sandbox readiness as `ready`, avoiding legacy setup prompts.
- Keep `allowed_sandbox_implementations` scoped to the legacy elevated and unelevated backends without restricting MXC.
- Default `windows.sandbox_private_desktop` to `false` for MXC while retaining `true` for legacy sandboxes.

## Testing

Add coverage for MXC configuration precedence, legacy requirement handling, sandbox selection, and TUI state. Add a Wine integration test that verifies command and patch routing fails when native MXC is unavailable and reports `windows_mxc` in turn metadata.

GitOrigin-RevId: e2162447d0750f60753864c92a20e02a7f297bca
This commit is contained in:
iceweasel-oai
2026-09-17 18:20:28 +00:00
committed by copyberry
parent 3d3ae4965a
commit 8b78600dc8
72 changed files with 557 additions and 142 deletions
+6 -6
View File
@@ -287,12 +287,12 @@ The origin of a required `chatgpt_base_url` must match the discovered origin by
## Windows sandbox implementation selection
`windowsSandbox/setupStart` and `windowsSandbox/readiness` apply only to the
legacy `elevated` and `unelevated` backends. Clients resolve the desired sandbox
implementation from configuration. When it is `mxc`, they skip both methods;
`allowedWindowsSandboxImplementations` can allow `mxc` independently of the
legacy setup modes. Non-Windows hosts report `notConfigured` for the legacy
readiness API.
`windowsSandbox/setupStart` applies only to the legacy `elevated` and
`unelevated` backends. `windowsSandbox/readiness` reports `ready` when MXC is
selected so clients do not offer legacy setup. The
`allowedWindowsSandboxImplementations` requirement governs only the legacy
backends and does not restrict MXC. Its `mxc` enum member is retained for wire
compatibility but is not emitted. Non-Windows hosts report `notConfigured`.
MXC uses the standard `command/exec` streaming and process-control path, including
ConPTY when `tty` is enabled. The buffered legacy Windows sandbox restrictions on
@@ -320,6 +320,7 @@ impl CommandExecRequestProcessor {
windows_sandbox_workspace_roots.as_slice(),
&codex_linux_sandbox_exe,
&self.arg0_paths.codex_self_exe,
self.config.permissions.windows_sandbox_type,
use_legacy_landlock,
)
.map_err(|err| internal_error(format!("exec failed: {err}")))?;
@@ -472,10 +472,10 @@ fn map_requirements_to_api(
implementations
.into_iter()
.map(|implementation| match implementation {
codex_config::types::WindowsSandboxModeToml::Elevated => {
codex_config::WindowsSandboxImplementationToml::Elevated => {
WindowsSandboxImplementation::Elevated
}
codex_config::types::WindowsSandboxModeToml::Unelevated => {
codex_config::WindowsSandboxImplementationToml::Unelevated => {
WindowsSandboxImplementation::Unelevated
}
})
@@ -1102,8 +1102,8 @@ mod tests {
let mapped = map_test_requirements(ConfigRequirementsToml {
windows: Some(WindowsRequirementsToml {
allowed_sandbox_implementations: Some(vec![
codex_config::types::WindowsSandboxModeToml::Elevated,
codex_config::types::WindowsSandboxModeToml::Unelevated,
codex_config::WindowsSandboxImplementationToml::Elevated,
codex_config::WindowsSandboxImplementationToml::Unelevated,
]),
sandbox_private_desktop: Some(false),
}),
@@ -21,6 +21,7 @@ use codex_app_server_protocol::ThreadSection;
use codex_app_server_protocol::ThreadSectionAppearance;
use codex_app_server_protocol::ThreadSectionMoveParams;
use codex_app_server_protocol::ThreadSectionMoveResponse;
use codex_config::types::WindowsSandboxModeToml;
use codex_extension_api::ExtensionDataInit;
use codex_extension_api::ThreadIdleCause;
use codex_protocol::SanitizedGitUrl;
@@ -4931,18 +4932,17 @@ impl ThreadRequestProcessor {
// Persist Windows sandbox mode.
let mut cli_overrides = cli_overrides.unwrap_or_default();
if cfg!(windows) {
match WindowsSandboxLevel::from_config(&self.config) {
WindowsSandboxLevel::Elevated => {
cli_overrides
.insert("windows.sandbox".to_string(), serde_json::json!("elevated"));
let mode = self.config.permissions.windows_sandbox_mode.or_else(|| {
match WindowsSandboxLevel::from_config(&self.config) {
WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated),
WindowsSandboxLevel::RestrictedToken => {
Some(WindowsSandboxModeToml::Unelevated)
}
WindowsSandboxLevel::Disabled => None,
}
WindowsSandboxLevel::RestrictedToken => {
cli_overrides.insert(
"windows.sandbox".to_string(),
serde_json::json!("unelevated"),
);
}
WindowsSandboxLevel::Disabled => {}
});
if let Some(mode) = mode {
cli_overrides.insert("windows.sandbox".to_string(), serde_json::json!(mode));
}
}
let request_overrides = if cli_overrides.is_empty() {
@@ -1,6 +1,7 @@
use super::*;
#[cfg(target_os = "windows")]
use anyhow::Context as _;
use codex_protocol::sandbox::SandboxType;
use codex_utils_path_uri::PathUri;
#[derive(Clone)]
@@ -305,6 +306,12 @@ fn determine_windows_sandbox_readiness(config: &Config) -> WindowsSandboxReadine
};
}
if config.permissions.windows_sandbox_type == SandboxType::WindowsMxc {
return WindowsSandboxReadinessResponse {
status: WindowsSandboxReadiness::Ready,
};
}
determine_windows_sandbox_readiness_from_state(
WindowsSandboxLevel::from_config(config),
sandbox_setup_is_complete(config.codex_home.as_path()),
+12 -7
View File
@@ -122,13 +122,18 @@ pub(super) fn sandbox_check(config: &Config, arg0_paths: &Arg0DispatchPaths) ->
#[cfg(target_os = "windows")]
{
let configured_backend = WindowsSandboxLevel::from_config(config);
let elevated = configured_backend != WindowsSandboxLevel::Disabled
&& windows_sandbox_uses_elevated_backend(configured_backend);
let backend = if elevated {
WindowsSandboxLevel::Elevated
let configured_level = WindowsSandboxLevel::from_config(config);
let mxc = config.permissions.windows_sandbox_type
== codex_protocol::sandbox::SandboxType::WindowsMxc;
let elevated = !mxc
&& configured_level != WindowsSandboxLevel::Disabled
&& windows_sandbox_uses_elevated_backend(configured_level);
let backend = if mxc {
"mxc".to_string()
} else if elevated {
WindowsSandboxLevel::Elevated.to_string()
} else {
configured_backend
configured_level.to_string()
};
let denied = !file_system_sandbox.has_full_disk_read_access();
@@ -137,7 +142,7 @@ pub(super) fn sandbox_check(config: &Config, arg0_paths: &Arg0DispatchPaths) ->
.details
.push(format!("denied-read restrictions: {denied}"));
if denied && !elevated {
if denied && !elevated && !mxc {
check.issues.push(
DoctorIssue::new(
CheckStatus::Fail,
+35 -8
View File
@@ -867,10 +867,17 @@ impl fmt::Display for WebSearchModeRequirement {
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct WindowsRequirementsToml {
pub allowed_sandbox_implementations: Option<Vec<WindowsSandboxModeToml>>,
pub allowed_sandbox_implementations: Option<Vec<WindowsSandboxImplementationToml>>,
pub sandbox_private_desktop: Option<bool>,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum WindowsSandboxImplementationToml {
Elevated,
Unelevated,
}
impl WindowsRequirementsToml {
pub fn is_empty(&self) -> bool {
self.allowed_sandbox_implementations.is_none() && self.sandbox_private_desktop.is_none()
@@ -1926,18 +1933,32 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
));
}
// Prefer elevated when both Windows sandbox implementations are allowed.
let initial_value =
if implementations.contains(&WindowsSandboxModeToml::Elevated) {
WindowsSandboxModeToml::Elevated
} else {
WindowsSandboxModeToml::Unelevated
};
let initial_value = if implementations
.contains(&WindowsSandboxImplementationToml::Elevated)
{
WindowsSandboxModeToml::Elevated
} else {
WindowsSandboxModeToml::Unelevated
};
let requirement_source_for_error = requirement_source.clone();
let constrained = Constrained::new(
Some(initial_value),
move |candidate| match candidate {
Some(candidate) if implementations.contains(candidate) => Ok(()),
Some(WindowsSandboxModeToml::Mxc) => Ok(()),
Some(WindowsSandboxModeToml::Elevated)
if implementations
.contains(&WindowsSandboxImplementationToml::Elevated) =>
{
Ok(())
}
Some(WindowsSandboxModeToml::Unelevated)
if implementations.contains(
&WindowsSandboxImplementationToml::Unelevated,
) =>
{
Ok(())
}
_ => Err(ConstraintError::InvalidValue {
field_name: "windows.sandbox",
candidate: format!("{candidate:?}"),
@@ -3779,6 +3800,12 @@ allowed_approvals_reviewers = ["user"]
.can_set(&Some(WindowsSandboxModeToml::Unelevated))
.is_err()
);
assert!(
requirements
.windows_sandbox_mode
.can_set(&Some(WindowsSandboxModeToml::Mxc))
.is_ok()
);
assert!(requirements.windows_sandbox_mode.can_set(&None).is_err());
Ok(())
+1
View File
@@ -126,6 +126,7 @@ pub use config_requirements::SandboxModeRequirement;
pub use config_requirements::Sourced;
pub use config_requirements::WebSearchModeRequirement;
pub use config_requirements::WindowsRequirementsToml;
pub use config_requirements::WindowsSandboxImplementationToml;
pub use config_requirements::sandbox_mode_requirement_for_permission_profile;
pub use constraint::Constrained;
pub use constraint::ConstraintError;
+3 -2
View File
@@ -163,14 +163,15 @@ impl Default for AuthKeyringBackendKind {
pub enum WindowsSandboxModeToml {
Elevated,
Unelevated,
Mxc,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct WindowsToml {
pub sandbox: Option<WindowsSandboxModeToml>,
/// Defaults to `true`. Set to `false` to launch the final sandboxed child
/// process on `Winsta0\\Default` instead of a private desktop.
/// Defaults to `true` for legacy sandboxes and `false` for MXC. Set to `false`
/// to launch the final sandboxed child process on `Winsta0\\Default`.
pub sandbox_private_desktop: Option<bool>,
}
+3 -2
View File
@@ -6098,7 +6098,8 @@
"WindowsSandboxModeToml": {
"enum": [
"elevated",
"unelevated"
"unelevated",
"mxc"
],
"type": "string"
},
@@ -6109,7 +6110,7 @@
"$ref": "#/definitions/WindowsSandboxModeToml"
},
"sandbox_private_desktop": {
"description": "Defaults to `true`. Set to `false` to launch the final sandboxed child process on `Winsta0\\\\Default` instead of a private desktop.",
"description": "Defaults to `true` for legacy sandboxes and `false` for MXC. Set to `false` to launch the final sandboxed child process on `Winsta0\\\\Default`.",
"type": "boolean"
}
},
+1
View File
@@ -375,6 +375,7 @@ fn resolved_local_environments<const N: usize>(
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
+11 -3
View File
@@ -7,6 +7,7 @@ use crate::unified_exec::MIN_EMPTY_YIELD_TIME_MS;
use crate::windows_sandbox::WindowsSandboxLevelExt;
use crate::windows_sandbox::resolve_windows_sandbox_mode;
use crate::windows_sandbox::resolve_windows_sandbox_private_desktop;
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
use codex_agent_roles::load_agent_roles;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLayerSource;
@@ -3352,6 +3353,10 @@ impl Config {
&mut constrained_windows_sandbox_mode,
&mut startup_warnings,
)?;
let legacy_windows_sandbox_level = windows_sandbox_level_for_legacy_checks(
windows_sandbox_type,
windows_sandbox_level,
);
let windows_sandbox_private_desktop = resolve_windows_sandbox_private_desktop(&cfg);
let resolved_cwd = AbsolutePathBuf::try_from(normalize_for_native_workdir({
use std::env;
@@ -3502,7 +3507,7 @@ impl Config {
.unwrap_or_else(|| {
default_builtin_permission_profile_name(
&active_project,
windows_sandbox_level,
legacy_windows_sandbox_level,
)
});
network_proxy_config_for_profile_selection(
@@ -3523,7 +3528,10 @@ impl Config {
let default_permissions = effective_permission_selection
.selected_profile_id
.unwrap_or_else(|| {
default_builtin_permission_profile_name(&active_project, windows_sandbox_level)
default_builtin_permission_profile_name(
&active_project,
legacy_windows_sandbox_level,
)
});
let builtin_workspace_write_settings = if using_implicit_builtin_profile {
cfg.sandbox_workspace_write.as_ref().map(|settings| WorkspaceWriteSettings {
@@ -3588,7 +3596,7 @@ impl Config {
let mut permission_profile = cfg
.derive_permission_profile(
sandbox_mode,
windows_sandbox_level,
legacy_windows_sandbox_level,
Some(&active_project),
Some(&constrained_permission_profile),
)
@@ -37,15 +37,19 @@ pub fn prepare_windows_sandbox_config(
} else {
configured_mode
};
let level = match effective_mode {
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken,
None => WindowsSandboxLevel::Disabled,
};
let sandbox_type = if level == WindowsSandboxLevel::Disabled {
SandboxType::None
} else {
SandboxType::WindowsRestrictedToken
let (sandbox_type, level) = match effective_mode {
Some(WindowsSandboxModeToml::Elevated) => (
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Elevated,
),
Some(WindowsSandboxModeToml::Unelevated) => (
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::RestrictedToken,
),
Some(WindowsSandboxModeToml::Mxc) => {
(SandboxType::WindowsMxc, WindowsSandboxLevel::Disabled)
}
None => (SandboxType::None, WindowsSandboxLevel::Disabled),
};
Ok(PreparedWindowsSandboxConfig {
mode,
@@ -23,6 +23,14 @@ fn configured_mode_takes_priority_without_persisting_feature_fallback() -> std::
level: WindowsSandboxLevel::RestrictedToken,
},
),
(
Some(WindowsSandboxModeToml::Mxc),
PreparedWindowsSandboxConfig {
mode: Some(WindowsSandboxModeToml::Mxc),
sandbox_type: SandboxType::WindowsMxc,
level: WindowsSandboxLevel::Disabled,
},
),
] {
let actual = prepare_windows_sandbox_config(
configured_mode,
@@ -1112,6 +1112,7 @@ mod tests {
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
@@ -1292,6 +1293,7 @@ url = "ws://127.0.0.1:8765"
allow_login_shell: false,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
@@ -1556,6 +1558,7 @@ url = "ws://127.0.0.1:8765"
allow_login_shell: false,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
@@ -1937,6 +1940,7 @@ url = "ws://127.0.0.1:8765"
allow_login_shell: false,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
@@ -2007,6 +2011,7 @@ url = "ws://127.0.0.1:8765"
allow_login_shell: false,
workspace_roots: selection.workspace_roots.clone(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_protocol::sandbox::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
+13 -3
View File
@@ -125,13 +125,13 @@ pub enum ExecCapturePolicy {
fn select_process_exec_tool_sandbox_type(
permission_profile: &PermissionProfile,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
windows_sandbox_type: SandboxType,
enforce_managed_network: bool,
) -> SandboxType {
SandboxManager::new().select_initial(
permission_profile,
SandboxablePreference::Auto,
windows_sandbox_level,
windows_sandbox_type,
enforce_managed_network,
)
}
@@ -308,6 +308,13 @@ pub async fn process_exec_tool_call(
use_legacy_landlock: bool,
stdout_stream: Option<StdoutStream>,
) -> Result<ExecToolCallOutput> {
let windows_sandbox_type = if params.windows_sandbox_level
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
{
SandboxType::None
} else {
SandboxType::WindowsRestrictedToken
};
let windows_sandbox_workspace_roots = windows_sandbox_workspace_roots
.iter()
.map(PathUri::from_abs_path)
@@ -319,6 +326,7 @@ pub async fn process_exec_tool_call(
&windows_sandbox_workspace_roots,
codex_linux_sandbox_exe,
codex_self_exe,
windows_sandbox_type,
use_legacy_landlock,
)?;
@@ -328,6 +336,7 @@ pub async fn process_exec_tool_call(
/// Transform a portable exec request into the concrete argv/env that should be
/// spawned under the requested sandbox policy.
#[allow(clippy::too_many_arguments)]
pub fn build_exec_request(
params: ExecParams,
permission_profile: &PermissionProfile,
@@ -335,6 +344,7 @@ pub fn build_exec_request(
windows_sandbox_workspace_roots: &[PathUri],
codex_linux_sandbox_exe: &Option<PathBuf>,
codex_self_exe: &Option<PathBuf>,
windows_sandbox_type: SandboxType,
use_legacy_landlock: bool,
) -> Result<ExecRequest> {
let ExecParams {
@@ -358,7 +368,7 @@ pub fn build_exec_request(
let enforce_managed_network = network.is_some();
let sandbox_type = select_process_exec_tool_sandbox_type(
permission_profile,
windows_sandbox_level,
windows_sandbox_type,
enforce_managed_network,
);
tracing::debug!("Sandbox type: {sandbox_type:?}");
+2 -1
View File
@@ -1187,7 +1187,7 @@ fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions()
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
),
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
SandboxType::None,
/*enforce_managed_network*/ false,
),
expected
@@ -1219,6 +1219,7 @@ fn build_exec_request_projects_workspace_roots_only_for_windows_sandbox() -> Res
roots,
&Some(temp_dir.path().join("codex-linux-sandbox")),
/*codex_self_exe*/ &None,
SandboxType::WindowsRestrictedToken,
/*use_legacy_landlock*/ false,
)
};
+1
View File
@@ -1348,6 +1348,7 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: turn_context.windows_sandbox_level,
windows_sandbox_type: turn_context.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: turn_context
.config
.permissions
+15 -12
View File
@@ -2,7 +2,7 @@
//! Labels never inspect the filesystem and must not be used for authorization.
use crate::responses_metadata::CodexResponsesMetadata;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_file_system::WindowsSandboxSelection;
use codex_protocol::models::PermissionProfile;
use codex_sandboxing::SandboxType;
use codex_sandboxing::get_platform_sandbox;
@@ -21,13 +21,13 @@ impl SandboxTags {
pub(crate) fn new(
profile: &PermissionProfile,
cwd: &Path,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_selection: WindowsSandboxSelection,
enforce_managed_network: bool,
) -> Self {
Self {
sandbox: permission_profile_sandbox_tag(
profile,
windows_sandbox_level,
windows_sandbox_selection,
enforce_managed_network,
),
policy: permission_profile_policy_tag(profile, cwd),
@@ -57,9 +57,10 @@ pub(crate) fn record_policy_metadata(
fn permission_profile_sandbox_tag(
profile: &PermissionProfile,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_selection: impl Into<WindowsSandboxSelection>,
enforce_managed_network: bool,
) -> &'static str {
let windows_sandbox_selection = windows_sandbox_selection.into();
match profile {
PermissionProfile::Disabled => return "none",
PermissionProfile::External { .. } => return "external",
@@ -77,16 +78,18 @@ fn permission_profile_sandbox_tag(
}
}
}
if cfg!(target_os = "windows") {
match windows_sandbox_level {
WindowsSandboxLevel::Elevated => return "windows_elevated",
WindowsSandboxLevel::Disabled | WindowsSandboxLevel::RestrictedToken => {}
match windows_sandbox_selection {
WindowsSandboxSelection::Mxc => SandboxType::WindowsMxc.as_metric_tag(),
WindowsSandboxSelection::Elevated => "windows_elevated",
WindowsSandboxSelection::RestrictedToken => {
SandboxType::WindowsRestrictedToken.as_metric_tag()
}
WindowsSandboxSelection::Disabled => {
get_platform_sandbox(/*windows_sandbox_enabled*/ false)
.map(SandboxType::as_metric_tag)
.unwrap_or("none")
}
}
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
.map(SandboxType::as_metric_tag)
.unwrap_or("none")
}
fn permission_profile_policy_tag(profile: &PermissionProfile, cwd: &Path) -> &'static str {
+13 -1
View File
@@ -114,6 +114,18 @@ pub(super) async fn spawn_review_thread(
step_settings.telemetry(&parent_turn_context.session_telemetry);
let per_turn_config = Arc::new(per_turn_config);
let review_turn_id = sub_id.to_string();
#[allow(deprecated)]
let windows_sandbox_selection = parent_turn_context
.environments
.primary()
.map(TurnEnvironment::windows_sandbox_selection_for_turn_metadata)
.unwrap_or_else(|| {
crate::tools::sandboxing::configured_windows_sandbox_selection(
parent_turn_context.config.permissions.windows_sandbox_type,
parent_turn_context.windows_sandbox_level,
&codex_utils_path_uri::PathUri::from_abs_path(&parent_turn_context.cwd),
)
});
let turn_metadata_state = Arc::new(TurnMetadataState::new(
sess.session_id().to_string(),
sess.thread_id().to_string(),
@@ -125,7 +137,7 @@ pub(super) async fn spawn_review_thread(
#[allow(deprecated)]
parent_turn_context.cwd.clone(),
&parent_turn_context.permission_profile(),
parent_turn_context.windows_sandbox_level,
windows_sandbox_selection,
parent_turn_context.network.is_some(),
auto_review_enabled,
&model_info,
+1
View File
@@ -177,6 +177,7 @@ impl SessionConfiguration {
permission_profile: self.permission_profile_state.snapshot(),
shell_environment_policy: self.shell_environment_policy.clone(),
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_type: self.windows_sandbox_type,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
use_legacy_landlock: self.use_legacy_landlock,
exec_policy: None,
+32 -1
View File
@@ -9,6 +9,7 @@ use crate::exec_policy::AllowPrefixRules;
use crate::shell_snapshot::ShellSnapshot;
use crate::shell_snapshot::ShellSnapshotFile;
use crate::shell_snapshot::ShellSnapshotSandbox;
use crate::tools::sandboxing::configured_windows_sandbox_selection;
use crate::tools::sandboxing::executor_windows_sandbox_selection;
use arc_swap::ArcSwap;
use codex_core_plugins::PluginCommandAttribution;
@@ -228,6 +229,7 @@ impl TurnEnvironment {
user_home_dir: self.user_home_dir.clone(),
temporary_directories: self.temporary_directories.clone(),
windows_sandbox_selection: executor_windows_sandbox_selection(
config.windows_sandbox_type,
config.windows_sandbox_level,
self.cwd(),
),
@@ -237,6 +239,25 @@ impl TurnEnvironment {
}
}
pub(crate) fn windows_sandbox_selection_for_turn_metadata(
&self,
) -> codex_file_system::WindowsSandboxSelection {
let config = self.config();
if self.environment.is_remote() {
executor_windows_sandbox_selection(
config.windows_sandbox_type,
config.windows_sandbox_level,
self.cwd(),
)
} else {
configured_windows_sandbox_selection(
config.windows_sandbox_type,
config.windows_sandbox_level,
self.cwd(),
)
}
}
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
self.config().permission_profile.active_permission_profile()
}
@@ -863,6 +884,16 @@ impl Session {
per_turn_config.permissions.approval_policy.value(),
per_turn_config.approvals_reviewer,
);
let windows_sandbox_selection = environments
.primary()
.map(TurnEnvironment::windows_sandbox_selection_for_turn_metadata)
.unwrap_or_else(|| {
configured_windows_sandbox_selection(
session_configuration.windows_sandbox_type,
session_configuration.windows_sandbox_level,
&PathUri::from_abs_path(&cwd),
)
});
let per_turn_config = Arc::new(per_turn_config);
let turn_metadata_state = Arc::new(TurnMetadataState::new(
session_id.to_string(),
@@ -874,7 +905,7 @@ impl Session {
sub_id.clone(),
cwd.clone(),
&permission_profile,
session_configuration.windows_sandbox_level,
windows_sandbox_selection,
network.is_some(),
auto_review_enabled,
model_info,
@@ -44,6 +44,7 @@ async fn snapshot_failure_omits_credentials_and_stops_descendants(
workspace_roots: std::slice::from_ref(&cwd_uri),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::None,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: cancellation.clone(),
@@ -299,6 +299,7 @@ async fn inactive_profiles_keep_snapshots_but_active_brokers_require_sandbox() -
allow_login_shell: false,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(permission_profile),
@@ -419,6 +420,7 @@ async fn inactive_profiles_keep_snapshots_but_active_brokers_require_sandbox() -
workspace_roots: std::slice::from_ref(&cwd_uri),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::None,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: Some(cancellation.clone()),
@@ -1349,6 +1349,7 @@ async fn spawn_internal_session_preserves_parent_lineage_without_forking_history
permission_profile: config.permissions.permission_profile_state().snapshot(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_type: config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
exec_policy: Some(codex_execpolicy::RequirementsExecPolicy::new(
@@ -37,6 +37,7 @@ use crate::tools::registry::ToolExecutor;
use crate::tools::runtimes::apply_patch::ApplyPatchRequest;
use crate::tools::runtimes::apply_patch::ApplyPatchRuntime;
use crate::tools::sandboxing::ToolCtx;
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
use codex_apply_patch::ApplyPatchAction;
use codex_apply_patch::ApplyPatchFileChange;
use codex_apply_patch::ApplyPatchFileUpdateMode;
@@ -556,7 +557,10 @@ async fn execute_verified_patch(
let sandbox_route = if turn_environment.environment.is_remote() {
PatchSandboxRoute::ExecutorManaged
} else {
PatchSandboxRoute::Platform(turn_environment.config().windows_sandbox_level)
PatchSandboxRoute::Platform(windows_sandbox_level_for_legacy_checks(
turn_environment.config().windows_sandbox_type,
turn_environment.config().windows_sandbox_level,
))
};
let (file_paths, effective_additional_permissions, file_system_sandbox_policy) =
effective_patch_permissions(
@@ -212,7 +212,10 @@ impl ExecCommandHandler {
&& SandboxManager::new().select_initial(
turn_environment.permission_profile(),
SandboxablePreference::Auto,
turn_environment.config().windows_sandbox_level,
codex_protocol::sandbox::effective_windows_sandbox_type(
turn_environment.config().windows_sandbox_type,
turn_environment.config().windows_sandbox_level,
),
turn.network.is_some(),
) != SandboxType::None;
// `to_abs_path()` alone cannot identify foreign drive paths: `file:///C:/repo` is
+9 -2
View File
@@ -90,6 +90,7 @@ impl ToolOrchestrator {
workspace_roots: attempt.workspace_roots,
sandbox_exe: attempt.sandbox_exe,
use_legacy_landlock: attempt.use_legacy_landlock,
windows_sandbox_type: attempt.windows_sandbox_type,
windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
network_denial_cancellation_token: network_approval
@@ -275,11 +276,15 @@ impl ToolOrchestrator {
managed_network_active,
),
};
let windows_sandbox_type = codex_protocol::sandbox::effective_windows_sandbox_type(
sandbox_config.windows_sandbox_type,
sandbox_config.windows_sandbox_level,
);
let initial_sandbox = if sandbox_requested && !executor_managed_process_sandbox {
sandbox_manager.select_initial(
&permissions,
sandbox_preference,
sandbox_config.windows_sandbox_level,
windows_sandbox_type,
managed_network_active,
)
} else {
@@ -306,6 +311,7 @@ impl ToolOrchestrator {
workspace_roots,
sandbox_exe: codex_sandbox_exe,
use_legacy_landlock: sandbox_config.use_legacy_landlock,
windows_sandbox_type,
windows_sandbox_level: sandbox_config.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop,
network_denial_cancellation_token: None,
@@ -458,7 +464,7 @@ impl ToolOrchestrator {
sandbox_manager.select_initial(
&permissions,
sandbox_preference,
sandbox_config.windows_sandbox_level,
windows_sandbox_type,
managed_network_active,
)
} else {
@@ -480,6 +486,7 @@ impl ToolOrchestrator {
workspace_roots,
sandbox_exe: retry_sandbox_exe,
use_legacy_landlock: sandbox_config.use_legacy_landlock,
windows_sandbox_type,
windows_sandbox_level: sandbox_config.windows_sandbox_level,
windows_sandbox_private_desktop: sandbox_config.windows_sandbox_private_desktop,
network_denial_cancellation_token: None,
@@ -103,6 +103,7 @@ impl ApplyPatchRuntime {
user_home_dir: req.turn_environment.user_home_dir.clone(),
temporary_directories: None,
windows_sandbox_selection: executor_windows_sandbox_selection(
attempt.windows_sandbox_type,
attempt.windows_sandbox_level,
attempt.sandbox_cwd,
),
@@ -29,6 +29,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
@@ -248,6 +249,7 @@ async fn file_system_sandbox_context_preserves_executor_workspace_permissions()
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
sandbox_exe: None,
use_legacy_landlock: true,
windows_sandbox_type: SandboxType::WindowsRestrictedToken,
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
windows_sandbox_private_desktop: true,
network_denial_cancellation_token: None,
@@ -316,6 +318,7 @@ async fn file_system_sandbox_context_respects_sandbox_request() {
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::None,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
@@ -126,6 +126,7 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
workspace_roots: std::slice::from_ref(&sandbox_policy_cwd),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::None,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
@@ -752,6 +752,7 @@ mod tests {
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_type: codex_sandboxing::SandboxType::None,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
@@ -768,7 +768,7 @@ impl CoreShellCommandExecutor {
let sandbox = sandbox_manager.select_initial(
permission_profile,
SandboxablePreference::Auto,
self.windows_sandbox_level,
SandboxType::None,
self.network.is_some(),
);
let cwd = PathUri::from_abs_path(workdir);
+25 -4
View File
@@ -399,6 +399,7 @@ pub(crate) struct SandboxAttempt<'a> {
// TODO(anp): Reconcile these attempt settings with TurnEnvironment::sandbox_context
// so process execution and patch writes honor the selected environment's backend.
pub use_legacy_landlock: bool,
pub windows_sandbox_type: SandboxType,
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub network_denial_cancellation_token: Option<CancellationToken>,
@@ -406,10 +407,12 @@ pub(crate) struct SandboxAttempt<'a> {
}
pub(crate) fn executor_windows_sandbox_level(
windows_sandbox_type: SandboxType,
windows_sandbox_level: WindowsSandboxLevel,
cwd: &PathUri,
) -> WindowsSandboxLevel {
if windows_sandbox_level == WindowsSandboxLevel::Disabled
if windows_sandbox_type != SandboxType::WindowsMxc
&& windows_sandbox_level == WindowsSandboxLevel::Disabled
&& cwd.infer_path_convention() == Some(PathConvention::Windows)
{
WindowsSandboxLevel::RestrictedToken
@@ -419,13 +422,30 @@ pub(crate) fn executor_windows_sandbox_level(
}
pub(crate) fn executor_windows_sandbox_selection(
windows_sandbox_type: SandboxType,
windows_sandbox_level: WindowsSandboxLevel,
cwd: &PathUri,
) -> WindowsSandboxSelection {
if cwd.infer_path_convention() == Some(PathConvention::Windows) {
executor_windows_sandbox_level(windows_sandbox_level, cwd).into()
configured_windows_sandbox_selection(
windows_sandbox_type,
executor_windows_sandbox_level(windows_sandbox_type, windows_sandbox_level, cwd),
cwd,
)
}
pub(crate) fn configured_windows_sandbox_selection(
windows_sandbox_type: SandboxType,
windows_sandbox_level: WindowsSandboxLevel,
cwd: &PathUri,
) -> WindowsSandboxSelection {
if cwd.infer_path_convention() != Some(PathConvention::Windows) {
return WindowsSandboxSelection::Disabled;
}
if windows_sandbox_type == SandboxType::WindowsMxc {
WindowsSandboxSelection::Mxc
} else {
WindowsSandboxSelection::Disabled
windows_sandbox_level.into()
}
}
@@ -526,6 +546,7 @@ impl<'a> SandboxAttempt<'a> {
user_home_dir: None,
temporary_directories: None,
windows_sandbox_selection: executor_windows_sandbox_selection(
self.windows_sandbox_type,
self.windows_sandbox_level,
self.sandbox_cwd,
),
@@ -73,6 +73,43 @@ fn restricted_sandbox_requires_exec_approval_on_request() {
);
}
#[test]
fn windows_sandbox_selection_distinguishes_configured_and_executor_defaults() {
let cwd = PathUri::parse("file:///C:/workspace").expect("Windows path URI");
assert_eq!(
executor_windows_sandbox_level(
SandboxType::WindowsMxc,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
&cwd,
),
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
);
assert_eq!(
executor_windows_sandbox_selection(
SandboxType::WindowsMxc,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
&cwd,
),
codex_file_system::WindowsSandboxSelection::Mxc,
);
assert_eq!(
configured_windows_sandbox_selection(
SandboxType::None,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
&cwd,
),
codex_file_system::WindowsSandboxSelection::Disabled,
);
assert_eq!(
executor_windows_sandbox_selection(
SandboxType::None,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
&cwd,
),
codex_file_system::WindowsSandboxSelection::RestrictedToken,
);
}
#[test]
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it() {
let policy = AskForApproval::Granular(GranularApprovalConfig {
@@ -257,6 +294,7 @@ fn windows_sandbox_env_preserves_denied_reads_or_rejects_unsupported_backend() {
workspace_roots: std::slice::from_ref(&cwd_uri),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::WindowsRestrictedToken,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
@@ -328,6 +366,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
workspace_roots: std::slice::from_ref(&cwd_uri),
sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_type: SandboxType::None,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
@@ -1256,6 +1256,7 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
allow_login_shell: true,
workspace_roots: Vec::new(),
windows_sandbox_level: turn.windows_sandbox_level,
windows_sandbox_type: turn.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: turn
.config
.permissions
+3 -3
View File
@@ -26,13 +26,13 @@ use crate::responses_metadata::subagent_metadata_kind;
use crate::sandbox_tags::SandboxTags;
use crate::sandbox_tags::record_policy_metadata;
use crate::session::step_settings::ResolvedStepSettings;
use codex_file_system::WindowsSandboxSelection;
use codex_git_utils::SanitizedGitUrl;
use codex_git_utils::get_git_remote_urls_assume_git_repo;
use codex_git_utils::get_has_changes_in_repo;
use codex_git_utils::get_head_commit_hash;
use codex_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -224,7 +224,7 @@ impl TurnMetadataState {
turn_id: String,
cwd: AbsolutePathBuf,
permission_profile: &PermissionProfile,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_selection: impl Into<WindowsSandboxSelection>,
enforce_managed_network: bool,
auto_review_enabled: bool,
model_info: &ModelInfo,
@@ -232,7 +232,7 @@ impl TurnMetadataState {
let sandbox_tags = SandboxTags::new(
permission_profile,
cwd.as_path(),
windows_sandbox_level,
windows_sandbox_selection.into(),
enforce_managed_network,
);
let agent_name = session_source
+1
View File
@@ -29,6 +29,7 @@ use codex_analytics::CompactionTrigger;
use codex_analytics::TurnAnalyticsMetadata;
use codex_models_manager::model_info::model_info_from_slug;
use codex_protocol::AgentPath;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::protocol::SessionSource;
@@ -68,6 +68,7 @@ use crate::unified_exec::process::UnifiedExecProcess;
use crate::unified_exec::shell_snapshot::shell_snapshot_request;
use crate::unified_exec::take_plugin_metrics_sidecar;
use crate::unified_exec::trace_id;
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
use codex_core_plugins::PLUGIN_METRICS_OUTPUT_ENV_VAR;
use codex_core_plugins::PluginCommandAttribution;
use codex_core_plugins::PluginMetricsSidecar;
@@ -1488,7 +1489,10 @@ impl UnifiedExecProcessManager {
approval_policy: context.step_context.settings.approval_policy(),
permission_profile: request.turn_environment.permission_profile().clone(),
environment_policy: request.turn_environment.config().exec_policy.as_ref(),
windows_sandbox_level: request.turn_environment.config().windows_sandbox_level,
windows_sandbox_level: windows_sandbox_level_for_legacy_checks(
request.turn_environment.config().windows_sandbox_type,
request.turn_environment.config().windows_sandbox_level,
),
sandbox_permissions: if request.additional_permissions_preapproved {
crate::sandboxing::SandboxPermissions::UseDefault
} else {
@@ -98,6 +98,7 @@ impl Session {
user_home_dir: None,
temporary_directories: None,
windows_sandbox_selection: executor_windows_sandbox_selection(
environment.config().windows_sandbox_type,
environment.config().windows_sandbox_level,
environment.cwd(),
),
@@ -11,6 +11,7 @@ use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use crate::tools::sandboxing::ApprovalAction;
use crate::tools::sandboxing::ToolError;
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
use codex_features::Feature;
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::EnvironmentNetworkPolicy;
@@ -52,10 +53,16 @@ impl TerminalPolicy {
additional_permissions: Option<AdditionalPermissionProfile>,
) -> Self {
let mut sandbox = environment.sandbox_context(additional_permissions);
if matches!(sandbox_source, TerminalSandboxSource::Native) {
if matches!(sandbox_source, TerminalSandboxSource::Native)
&& windows_sandbox_level_for_legacy_checks(
environment.config().windows_sandbox_type,
environment.config().windows_sandbox_level,
) == codex_protocol::config_types::WindowsSandboxLevel::Disabled
{
// The filesystem helper applies executor defaults, but native process
// launches honor Disabled. Preserve it so later enablement is detected.
sandbox.windows_sandbox_selection = environment.config().windows_sandbox_level.into();
sandbox.windows_sandbox_selection =
codex_protocol::config_types::WindowsSandboxLevel::Disabled.into();
}
Self {
sandbox,
@@ -10,6 +10,7 @@ use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_sandboxing::SandboxType;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -221,11 +222,13 @@ async fn readable_snapshot_does_not_require_stdin_approval() -> anyhow::Result<(
Ok(())
}
#[test_case::test_case(TerminalSandboxSource::Native, SandboxPermissions::RequireEscalated; "native_disabled_sandbox_needs_review_when_enabled")]
#[test_case::test_case(TerminalSandboxSource::Executor, SandboxPermissions::UseDefault; "executor_keeps_its_restricted_token_default")]
#[test_case::test_case(TerminalSandboxSource::Native, SandboxType::None, SandboxPermissions::RequireEscalated; "native_disabled_sandbox_needs_review_when_enabled")]
#[test_case::test_case(TerminalSandboxSource::Executor, SandboxType::None, SandboxPermissions::UseDefault; "executor_keeps_its_restricted_token_default")]
#[test_case::test_case(TerminalSandboxSource::Native, SandboxType::WindowsMxc, SandboxPermissions::UseDefault; "native_mxc_ignores_legacy_level_changes")]
#[tokio::test]
async fn enabling_windows_sandbox_respects_the_launch_backend(
source: TerminalSandboxSource,
sandbox_type: SandboxType,
expected: SandboxPermissions,
) -> anyhow::Result<()> {
let (_session, turn) = make_session_and_context().await;
@@ -233,7 +236,16 @@ async fn enabling_windows_sandbox_respects_the_launch_backend(
environment.selection.cwd = PathUri::parse("file:///C:/workspace")?;
environment.config_mut().permission_profile =
PermissionProfileSnapshot::legacy(PermissionProfile::read_only());
environment.config_mut().windows_sandbox_type = sandbox_type;
environment.config_mut().windows_sandbox_level = WindowsSandboxLevel::Disabled;
assert_eq!(
environment.windows_sandbox_selection_for_turn_metadata(),
if sandbox_type == SandboxType::WindowsMxc {
codex_file_system::WindowsSandboxSelection::Mxc
} else {
codex_file_system::WindowsSandboxSelection::Disabled
}
);
let permissions = TerminalPermissions::for_launch(
&environment,
&turn,
+16 -1
View File
@@ -9,6 +9,7 @@ use codex_login::default_client::originator;
use codex_otel::sanitize_metric_tag_value;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::sandbox::effective_windows_sandbox_type;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::BTreeMap;
@@ -27,6 +28,19 @@ pub fn managed_proxy_routing_for_windows_sandbox(
}
}
/// Adapts the selected implementation for legacy checks that only understand setup levels.
/// Backend selection must continue to use [`SandboxType`] directly.
pub(crate) fn windows_sandbox_level_for_legacy_checks(
sandbox_type: SandboxType,
sandbox_level: WindowsSandboxLevel,
) -> WindowsSandboxLevel {
if effective_windows_sandbox_type(sandbox_type, sandbox_level) == SandboxType::WindowsMxc {
WindowsSandboxLevel::RestrictedToken
} else {
sandbox_level
}
}
pub trait WindowsSandboxLevelExt {
fn from_config(config: &Config) -> WindowsSandboxLevel;
fn from_features(features: &Features) -> WindowsSandboxLevel;
@@ -37,6 +51,7 @@ impl WindowsSandboxLevelExt for WindowsSandboxLevel {
match config.permissions.windows_sandbox_mode {
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken,
Some(WindowsSandboxModeToml::Mxc) => WindowsSandboxLevel::Disabled,
None => Self::from_features(&config.features),
}
}
@@ -64,7 +79,7 @@ pub fn resolve_windows_sandbox_private_desktop(cfg: &ConfigToml) -> bool {
cfg.windows
.as_ref()
.and_then(|windows| windows.sandbox_private_desktop)
.unwrap_or(true)
.unwrap_or_else(|| resolve_windows_sandbox_mode(cfg) != Some(WindowsSandboxModeToml::Mxc))
}
pub fn legacy_windows_sandbox_mode(
@@ -385,6 +385,7 @@ async fn v2_residency_reload_preserves_inherited_environment_and_tools(
permission_profile: PermissionProfileSnapshot::legacy(child_permissions),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -52,11 +52,13 @@ use codex_utils_path_uri::PathUri;
use core_test_support::PathBufExt;
use core_test_support::TestTargetOs;
use core_test_support::assert_regex_match;
use core_test_support::is_wine_exec_test_environment;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_exec_command_call_with_args;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_function_call_agent_response;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
@@ -244,6 +246,68 @@ pub async fn mount_apply_patch(
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn mxc_config_routes_command_and_patch_to_the_windows_executor() -> Result<()> {
if !is_wine_exec_test_environment() {
return Ok(());
}
let harness = apply_patch_harness_with(|builder| {
builder.with_pre_build_hook(|home| {
fs::write(home.join("config.toml"), "[windows]\nsandbox = \"mxc\"\n")
.expect("write MXC config");
})
})
.await?;
let exec_response = mount_function_call_agent_response(
harness.server(),
"mxc-exec",
&json!({"cmd": "Write-Output should-not-run"}).to_string(),
"exec_command",
)
.await;
harness
.submit_with_permission_profile(
"exercise MXC command routing",
PermissionProfile::workspace_write(),
)
.await?;
mount_apply_patch(
&harness,
"mxc-patch",
"*** Begin Patch\n*** Add File: should-not-exist.txt\n+blocked\n*** End Patch",
"done",
)
.await;
harness
.submit_with_permission_profile(
"exercise MXC patch routing",
PermissionProfile::workspace_write(),
)
.await?;
let exec_output = harness.function_call_stdout("mxc-exec").await;
assert!(
exec_output.contains("native MXC is unavailable"),
"expected MXC unavailability error, got: {exec_output:?}"
);
let patch_output = harness.apply_patch_output("mxc-patch").await;
assert!(
patch_output.contains("Failed to write file"),
"expected apply_patch failure, got: {patch_output:?}"
);
let metadata: serde_json::Value = serde_json::from_str(
&exec_response
.function_call
.single_request()
.header("x-codex-turn-metadata")
.expect("turn metadata header"),
)?;
assert_eq!(metadata["sandbox"], "windows_mxc");
Ok(())
}
async fn mount_apply_patch_model_output(
harness: &TestCodexHarness,
call_id: &str,
+3
View File
@@ -720,6 +720,7 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -750,6 +751,7 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -861,6 +863,7 @@ async fn environment_command_policy_changes_invalidate_session_approvals() -> Re
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1633,6 +1633,7 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
@@ -838,6 +838,7 @@ impl ExecutorHookFixture {
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&self.test.config),
windows_sandbox_type: self.test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: self
.test
.config
@@ -366,6 +366,7 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()>
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
windows_sandbox_type: fixture.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: fixture.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: fixture.config.features.use_legacy_landlock(),
exec_policy: None,
@@ -497,6 +497,7 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> {
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -2429,6 +2429,7 @@ async fn owner_network_policy_follows_the_selected_remote_command() -> Result<()
}),
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
+8
View File
@@ -597,6 +597,7 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()>
permission_profile: owner_permission_profile,
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1461,6 +1462,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1506,6 +1508,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1535,6 +1538,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1603,6 +1607,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -1679,6 +1684,7 @@ async fn owner_network_policy_rejects_unsupported_environment_authority() -> Res
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
@@ -1768,6 +1774,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
@@ -2435,6 +2442,7 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments(
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test
.config
.permissions
+2
View File
@@ -828,6 +828,7 @@ async fn environment_mcp_policy_filters_runtime_config_and_model_tools(
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
windows_sandbox_type: fixture.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: fixture
.config
.permissions
@@ -1890,6 +1891,7 @@ async fn stdio_mcp_tool_call_includes_sandbox_state_meta(
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
windows_sandbox_type: fixture.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: fixture
.config
.permissions
@@ -700,6 +700,7 @@ async fn shell_snapshot_v2_guardian_uses_its_resolved_permissions_and_tools(
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_type: test.config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
@@ -505,6 +505,7 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
..Default::default()
},
windows_sandbox_level: WindowsSandboxLevel::from_config(&harness.test().config),
windows_sandbox_type: harness.test().config.permissions.windows_sandbox_type,
windows_sandbox_private_desktop: harness
.test()
.config
@@ -7,6 +7,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::sandbox::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
#[cfg(windows)]
@@ -277,6 +278,7 @@ async fn workspace_roots_allow_file_and_command_writes_in_secondary_root(
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
windows_sandbox_type: SandboxType::WindowsRestrictedToken,
windows_sandbox_private_desktop: test
.config
.permissions
@@ -20,10 +20,16 @@ pub(crate) fn select_sandbox(
WindowsSandboxSelection::Elevated => WindowsSandboxLevel::Elevated,
WindowsSandboxSelection::Mxc => return (SandboxType::WindowsMxc, None),
};
let windows_sandbox_type = match windows_sandbox_level {
WindowsSandboxLevel::Disabled => SandboxType::None,
WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Elevated => {
SandboxType::WindowsRestrictedToken
}
};
let sandbox_type = manager.select_initial(
permission_profile,
SandboxablePreference::Require,
windows_sandbox_level,
windows_sandbox_type,
has_managed_network_requirements,
);
(sandbox_type, Some(windows_sandbox_level))
+1
View File
@@ -54,6 +54,7 @@ pub(super) async fn spawn_command_under_sandbox(
&[PathUri::from_abs_path(sandbox_cwd)],
&codex_linux_sandbox_exe,
/*codex_self_exe*/ &None,
codex_protocol::sandbox::SandboxType::None,
/*use_legacy_landlock*/ false,
)
.map_err(|err| io::Error::other(err.to_string()))?;
+5 -1
View File
@@ -7,6 +7,7 @@ use crate::mcp_policy::EnvironmentMcpPolicy;
use crate::models::PermissionProfile;
use crate::models::PermissionProfileSnapshot;
use crate::protocol::AskForApproval;
use crate::sandbox::SandboxType;
use codex_execpolicy::RequirementsExecPolicy;
use codex_network_proxy::EnvironmentNetworkPolicy;
use codex_utils_path_uri::PathUri;
@@ -64,8 +65,10 @@ pub struct EnvironmentConfig {
pub permission_profile: PermissionProfileSnapshot,
/// Controls which environment variables shell commands may inherit.
pub shell_environment_policy: ShellEnvironmentPolicy,
/// Windows sandbox implementation for this environment attachment.
/// Legacy Windows restricted-token setup level for this environment attachment.
pub windows_sandbox_level: WindowsSandboxLevel,
/// Concrete Windows sandbox backend selected for this environment attachment.
pub windows_sandbox_type: SandboxType,
/// Whether Windows sandbox processes use a private desktop.
pub windows_sandbox_private_desktop: bool,
/// Whether Linux sandbox processes use the legacy Landlock backend.
@@ -89,6 +92,7 @@ impl std::fmt::Debug for EnvironmentConfig {
.field("permission_profile", &self.permission_profile)
.field("shell_environment_policy", &"<redacted>")
.field("windows_sandbox_level", &self.windows_sandbox_level)
.field("windows_sandbox_type", &self.windows_sandbox_type)
.field(
"windows_sandbox_private_desktop",
&self.windows_sandbox_private_desktop,
+1
View File
@@ -48,6 +48,7 @@ pub mod realtime;
pub mod request_permissions;
pub mod request_user_input;
pub mod review_format;
pub mod sandbox;
pub mod security_risk;
pub mod shell_environment;
pub mod turn_input;
+42
View File
@@ -0,0 +1,42 @@
//! Identifies the platform sandbox implementation selected for execution.
use serde::Deserialize;
use serde::Serialize;
use crate::config_types::WindowsSandboxLevel;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
WindowsMxc,
}
impl SandboxType {
pub fn as_metric_tag(self) -> &'static str {
match self {
Self::None => "none",
Self::MacosSeatbelt => "seatbelt",
Self::LinuxSeccomp => "seccomp",
Self::WindowsRestrictedToken => "windows_sandbox",
Self::WindowsMxc => "windows_mxc",
}
}
}
/// Preserves explicit MXC selection while honoring legacy runtime level updates.
pub fn effective_windows_sandbox_type(
sandbox_type: SandboxType,
sandbox_level: WindowsSandboxLevel,
) -> SandboxType {
match (sandbox_type, sandbox_level) {
(SandboxType::WindowsMxc, _) => SandboxType::WindowsMxc,
(_, WindowsSandboxLevel::Disabled) => SandboxType::None,
(_, WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Elevated) => {
SandboxType::WindowsRestrictedToken
}
}
}
+6 -24
View File
@@ -22,6 +22,7 @@ use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
pub use codex_protocol::sandbox::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
@@ -37,27 +38,6 @@ const WINDOWS_SANDBOX_WRAPPER_SETUP_ENV_ALLOWLIST: &[&str] = &[
"SYSTEMROOT",
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
WindowsMxc,
}
impl SandboxType {
pub fn as_metric_tag(self) -> &'static str {
match self {
SandboxType::None => "none",
SandboxType::MacosSeatbelt => "seatbelt",
SandboxType::LinuxSeccomp => "seccomp",
SandboxType::WindowsRestrictedToken => "windows_sandbox",
SandboxType::WindowsMxc => "windows_mxc",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SandboxablePreference {
Auto,
@@ -324,7 +304,7 @@ impl SandboxManager {
&self,
permission_profile: &PermissionProfile,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_type: SandboxType,
has_managed_network_requirements: bool,
) -> SandboxType {
#[cfg(windows)]
@@ -333,8 +313,10 @@ impl SandboxManager {
if !self.should_sandbox(permission_profile, pref, has_managed_network_requirements) {
return SandboxType::None;
}
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
.unwrap_or(SandboxType::None)
if cfg!(windows) && windows_sandbox_type == SandboxType::WindowsMxc {
return SandboxType::WindowsMxc;
}
get_platform_sandbox(windows_sandbox_type != SandboxType::None).unwrap_or(SandboxType::None)
}
/// Returns whether the request needs a sandbox, independently of whether
+20 -3
View File
@@ -31,7 +31,7 @@ fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
let sandbox = manager.select_initial(
&PermissionProfile::Disabled,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
SandboxType::None,
/*has_managed_network_requirements*/ false,
);
assert_eq!(sandbox, SandboxType::None);
@@ -45,7 +45,7 @@ fn danger_full_access_uses_platform_sandbox_with_network_requirements() {
let sandbox = manager.select_initial(
&PermissionProfile::Disabled,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
SandboxType::None,
/*has_managed_network_requirements*/ true,
);
assert_eq!(sandbox, expected);
@@ -69,12 +69,29 @@ fn restricted_file_system_uses_platform_sandbox_without_managed_network() {
let sandbox = manager.select_initial(
&permissions,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
SandboxType::None,
/*has_managed_network_requirements*/ false,
);
assert_eq!(sandbox, expected);
}
#[test]
fn explicit_mxc_only_overrides_the_windows_sandbox() {
let manager = SandboxManager::new();
let sandbox = manager.select_initial(
&PermissionProfile::read_only(),
SandboxablePreference::Auto,
SandboxType::WindowsMxc,
/*has_managed_network_requirements*/ false,
);
let expected = if cfg!(windows) {
SandboxType::WindowsMxc
} else {
get_platform_sandbox(/*windows_sandbox_enabled*/ false).unwrap_or(SandboxType::None)
};
assert_eq!(sandbox, expected);
}
#[test]
fn unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_policy() {
let manager = SandboxManager::new();
+1 -1
View File
@@ -104,7 +104,7 @@ impl App {
windows_sandbox_ready(app_server).await;
}
let show_nux = std::mem::take(&mut self.windows_sandbox.prompt_after_trust)
&& self.chat_widget.windows_sandbox_config.mode.is_none()
&& !self.chat_widget.windows_sandbox_config.is_enabled()
|| self.chat_widget.windows_sandbox_config.requires_elevated();
if self.windows_sandbox_setup_is_local() {
self.chat_widget.maybe_prompt_windows_sandbox_enable(
+1
View File
@@ -1666,6 +1666,7 @@ async fn windows_sandbox_config_refresh_uses_connected_server() -> Result<()> {
Some(codex_app_server_protocol::WindowsSandboxSetupMode::Elevated);
assert!(app.refresh_windows_sandbox_config(&server).await);
let loaded_config = crate::windows_sandbox::WindowsSandboxConfig {
mxc_selected: false,
mode: Some(codex_app_server_protocol::WindowsSandboxSetupMode::Unelevated),
requirements: Some(None),
};
@@ -368,7 +368,7 @@ impl ChatWidget {
// cannot be set up from this TUI's Windows account.
return apply_actions();
}
if self.windows_sandbox_config.level() == WindowsSandboxLevel::Disabled {
if !self.windows_sandbox_config.is_enabled() {
let preset = preset.clone();
return vec![Box::new(move |tx| {
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
@@ -64,7 +64,7 @@ impl ChatWidget {
| crate::app::WindowsSandboxHost::Unknown
) || (self.windows_sandbox_host != crate::app::WindowsSandboxHost::Remote
&& reviewer == ApprovalsReviewer::User
&& self.windows_sandbox_config.level() == WindowsSandboxLevel::Disabled))
&& !self.windows_sandbox_config.is_enabled()))
{
continue;
}
@@ -272,8 +272,7 @@ impl ChatWidget {
#[cfg(target_os = "windows")]
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
let windows_sandbox_level = self.windows_sandbox_config.level();
let setup_is_required = windows_sandbox_level == WindowsSandboxLevel::Disabled
let setup_is_required = !self.windows_sandbox_config.is_enabled()
|| self.elevated_windows_sandbox_setup_required();
if show_now
&& setup_is_required
@@ -18,8 +18,6 @@ use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_exec_server::LOCAL_FS;
use codex_git_utils::resolve_root_git_project_for_trust;
#[cfg(target_os = "windows")]
use codex_protocol::config_types::WindowsSandboxLevel;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind;
@@ -149,7 +147,7 @@ impl OnboardingScreen {
config.cwd.display().to_string(),
)
.await
.is_ok_and(|state| state.level() == WindowsSandboxLevel::Disabled)
.is_ok_and(|state| !state.is_enabled())
} else {
false
};
+20 -6
View File
@@ -12,6 +12,7 @@ use uuid::Uuid;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct WindowsSandboxConfig {
pub(crate) mxc_selected: bool,
pub(crate) mode: Option<WindowsSandboxSetupMode>,
// None means policy has not been loaded; a loaded null list allows both modes.
pub(crate) requirements: Option<Option<Vec<WindowsSandboxSetupMode>>>,
@@ -23,14 +24,22 @@ impl WindowsSandboxConfig {
config: &ConfigReadResponse,
requirements: ConfigRequirementsReadResponse,
) -> Self {
let configured_sandbox = config
.config
.additional
.get("windows")
.and_then(|windows| windows.get("sandbox"));
let mxc_selected = configured_sandbox
.and_then(|implementation| serde_json::from_value(implementation.clone()).ok())
== Some(WindowsSandboxImplementation::Mxc);
let mut state = Self {
mode: config
.config
.additional
.get("windows")
.and_then(|windows| windows.get("sandbox"))
mxc_selected,
mode: configured_sandbox
.and_then(|mode| serde_json::from_value(mode.clone()).ok())
.or_else(|| {
if mxc_selected {
return None;
}
let features = config.config.additional.get("features")?;
[
(
@@ -72,7 +81,8 @@ impl WindowsSandboxConfig {
}),
),
};
if let Some(Some(allowed)) = &state.requirements
if !state.mxc_selected
&& let Some(Some(allowed)) = &state.requirements
&& !state.mode.is_some_and(|mode| allowed.contains(&mode))
{
// Managed requirements prefer elevated when the configured value is disallowed.
@@ -86,6 +96,10 @@ impl WindowsSandboxConfig {
state
}
pub(crate) fn is_enabled(&self) -> bool {
self.mxc_selected || self.mode.is_some()
}
pub(crate) fn level(&self) -> WindowsSandboxLevel {
match self.mode {
Some(WindowsSandboxSetupMode::Elevated) => WindowsSandboxLevel::Elevated,
+19 -9
View File
@@ -11,36 +11,45 @@ fn windows_sandbox_server_policy_controls_setup_choices() {
Some("unelevated"),
serde_json::Value::Null,
Some(Unelevated),
(true, true, false),
(true, true, false, true),
),
(
Some("unelevated"),
json!(["elevated"]),
Some(Elevated),
(true, false, true),
(true, false, true, true),
),
(
None,
json!(["unelevated", "elevated"]),
Some(Elevated),
(true, true, true),
(true, true, true, true),
),
(
None,
json!(["unelevated"]),
Some(Unelevated),
(false, true, false),
(false, true, false, true),
),
(Some("unelevated"), json!([]), None, (false, false, false)),
(
Some("unelevated"),
json!(["mxc"]),
json!([]),
None,
(false, false, false),
(false, false, false, false),
),
(
Some("mxc"),
json!(["elevated"]),
None,
(true, false, false, true),
),
] {
let config = serde_json::from_value(json!({
"config": {"windows": {"sandbox": configured}}, "origins": {}
"config": {
"windows": {"sandbox": configured},
"features": {"elevated_windows_sandbox": configured == Some("mxc")}
},
"origins": {}
}))
.unwrap();
let requirements = serde_json::from_value(json!({
@@ -53,7 +62,8 @@ fn windows_sandbox_server_policy_controls_setup_choices() {
(
state.allows(Elevated),
state.allows(Unelevated),
state.requires_elevated()
state.requires_elevated(),
state.is_enabled(),
),
expected
);
@@ -6,7 +6,7 @@ use anyhow::bail;
use codex_cloud_config::cloud_config_bundle_loader_for_storage_without_cache;
use codex_config::ConfigLoadOptions;
use codex_config::ConfigRequirementsToml;
use codex_config::types::WindowsSandboxModeToml;
use codex_config::WindowsSandboxImplementationToml;
use codex_core::config::bootstrap_auth_config;
use codex_core::config::load_config_toml_with_layer_stack;
use codex_windows_sandbox::WindowsSandboxProvisioningSettings;
@@ -92,7 +92,7 @@ fn validate_requirements(
.windows
.as_ref()
.and_then(|windows| windows.allowed_sandbox_implementations.as_ref())
.is_some_and(|allowed| !allowed.contains(&WindowsSandboxModeToml::Elevated))
.is_some_and(|allowed| !allowed.contains(&WindowsSandboxImplementationToml::Elevated))
{
bail!("managed policy does not permit the elevated Windows sandbox");
}