Promote Windows Sandbox (#11341)

1. Move Windows Sandbox NUX to right after trust directory screen
2. Don't offer read-only as an option in Sandbox NUX.
Elevated/Legacy/Quit
3. Don't allow new untrusted directories. It's trust or quit
4. move experimental sandbox features to `[windows]
sandbox="elevated|unelevatd"`
5. Copy tweaks = elevated -> default, non-elevated -> non-admin
This commit is contained in:
iceweasel-oai
2026-02-11 11:48:33 -08:00
committed by GitHub
parent 24e6adbda5
commit 87279de434
21 changed files with 727 additions and 395 deletions

View File

@@ -19,6 +19,8 @@ use crate::config::types::ShellEnvironmentPolicyToml;
use crate::config::types::SkillsConfig;
use crate::config::types::Tui;
use crate::config::types::UriBasedFileOpener;
use crate::config::types::WindowsSandboxModeToml;
use crate::config::types::WindowsToml;
use crate::config_loader::CloudRequirementsLoader;
use crate::config_loader::ConfigLayerStack;
use crate::config_loader::ConfigRequirements;
@@ -45,6 +47,7 @@ use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
use crate::protocol::AskForApproval;
use crate::protocol::SandboxPolicy;
use crate::windows_sandbox::WindowsSandboxLevelExt;
use crate::windows_sandbox::resolve_windows_sandbox_mode;
use codex_app_server_protocol::Tools;
use codex_app_server_protocol::UserSavedConfig;
use codex_protocol::config_types::AltScreenMode;
@@ -161,10 +164,6 @@ pub struct Config {
/// for either of approval_policy or sandbox_mode.
pub did_user_set_custom_approval_policy_or_sandbox_mode: bool,
/// On Windows, indicates that a previously configured workspace-write sandbox
/// was coerced to read-only because native auto mode is unsupported.
pub forced_auto_mode_downgraded_on_windows: bool,
pub shell_environment_policy: ShellEnvironmentPolicy,
/// When `true`, `AgentReasoning` events emitted by the backend will be
@@ -342,6 +341,10 @@ pub struct Config {
/// Settings for ghost snapshots (used for undo).
pub ghost_snapshot: GhostSnapshotConfig,
/// Effective Windows sandbox mode derived from `[windows].sandbox` or
/// legacy feature keys.
pub windows_sandbox_mode: Option<WindowsSandboxModeToml>,
/// Centralized feature flags; source of truth for feature gating.
pub features: Features,
@@ -1037,6 +1040,10 @@ pub struct ConfigToml {
/// OTEL configuration.
pub otel: Option<crate::config::types::OtelConfigToml>,
/// Windows-specific configuration.
#[serde(default)]
pub windows: Option<WindowsToml>,
/// Tracks whether the Windows onboarding screen has been acknowledged.
pub windows_wsl_setup_acknowledged: Option<bool>,
@@ -1139,12 +1146,6 @@ pub struct GhostSnapshotToml {
pub disable_warnings: Option<bool>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SandboxPolicyResolution {
pub policy: SandboxPolicy,
pub forced_auto_mode_downgraded_on_windows: bool,
}
impl ConfigToml {
/// Derive the effective sandbox policy from the configuration.
fn derive_sandbox_policy(
@@ -1154,7 +1155,7 @@ impl ConfigToml {
windows_sandbox_level: WindowsSandboxLevel,
resolved_cwd: &Path,
sandbox_policy_constraint: Option<&Constrained<SandboxPolicy>>,
) -> SandboxPolicyResolution {
) -> SandboxPolicy {
let sandbox_mode_was_explicit = sandbox_mode_override.is_some()
|| profile_sandbox_mode.is_some()
|| self.sandbox_mode.is_some();
@@ -1162,10 +1163,19 @@ impl ConfigToml {
.or(profile_sandbox_mode)
.or(self.sandbox_mode)
.or_else(|| {
// if no sandbox_mode is set, but user has marked directory as trusted or untrusted, use WorkspaceWrite
// If no sandbox_mode is set but this directory has a trust decision,
// default to workspace-write except on unsandboxed Windows where we
// default to read-only.
self.get_active_project(resolved_cwd).and_then(|p| {
if p.is_trusted() || p.is_untrusted() {
Some(SandboxMode::WorkspaceWrite)
if cfg!(target_os = "windows")
&& windows_sandbox_level
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
{
Some(SandboxMode::ReadOnly)
} else {
Some(SandboxMode::WorkspaceWrite)
}
} else {
None
}
@@ -1190,8 +1200,7 @@ impl ConfigToml {
},
SandboxMode::DangerFullAccess => SandboxPolicy::DangerFullAccess,
};
let mut forced_auto_mode_downgraded_on_windows = false;
let mut downgrade_workspace_write_if_unsupported = |policy: &mut SandboxPolicy| {
let downgrade_workspace_write_if_unsupported = |policy: &mut SandboxPolicy| {
if cfg!(target_os = "windows")
// If the experimental Windows sandbox is enabled, do not force a downgrade.
&& windows_sandbox_level
@@ -1199,7 +1208,6 @@ impl ConfigToml {
&& matches!(&*policy, SandboxPolicy::WorkspaceWrite { .. })
{
*policy = SandboxPolicy::new_read_only_policy();
forced_auto_mode_downgraded_on_windows = true;
}
};
if matches!(resolved_sandbox_mode, SandboxMode::WorkspaceWrite) {
@@ -1216,10 +1224,7 @@ impl ConfigToml {
sandbox_policy = constraint.get().clone();
downgrade_workspace_write_if_unsupported(&mut sandbox_policy);
}
SandboxPolicyResolution {
policy: sandbox_policy,
forced_auto_mode_downgraded_on_windows,
}
sandbox_policy
}
/// Resolves the cwd to an existing project, or returns None if ConfigToml
@@ -1438,6 +1443,7 @@ impl Config {
};
let features = Features::from_config(&cfg, &config_profile, feature_overrides);
let windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg, &config_profile);
let resolved_cwd = {
use std::env;
@@ -1467,11 +1473,12 @@ impl Config {
|| config_profile.sandbox_mode.is_some()
|| cfg.sandbox_mode.is_some();
let windows_sandbox_level = WindowsSandboxLevel::from_features(&features);
let SandboxPolicyResolution {
policy: mut sandbox_policy,
forced_auto_mode_downgraded_on_windows,
} = cfg.derive_sandbox_policy(
let windows_sandbox_level = match windows_sandbox_mode {
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken,
None => WindowsSandboxLevel::from_features(&features),
};
let mut sandbox_policy = cfg.derive_sandbox_policy(
sandbox_mode,
config_profile.sandbox_mode,
windows_sandbox_level,
@@ -1711,7 +1718,6 @@ impl Config {
enforce_residency: enforce_residency.value,
network,
did_user_set_custom_approval_policy_or_sandbox_mode,
forced_auto_mode_downgraded_on_windows,
shell_environment_policy,
notify: cfg.notify,
user_instructions,
@@ -1776,6 +1782,7 @@ impl Config {
web_search_mode: constrained_web_search_mode.value,
use_experimental_unified_exec_tool,
ghost_snapshot,
windows_sandbox_mode,
features,
suppress_unstable_features_warning: cfg
.suppress_unstable_features_warning
@@ -1881,21 +1888,29 @@ impl Config {
}
pub fn set_windows_sandbox_enabled(&mut self, value: bool) {
if value {
self.features.enable(Feature::WindowsSandbox);
self.forced_auto_mode_downgraded_on_windows = false;
self.windows_sandbox_mode = if value {
Some(WindowsSandboxModeToml::Unelevated)
} else if matches!(
self.windows_sandbox_mode,
Some(WindowsSandboxModeToml::Unelevated)
) {
None
} else {
self.features.disable(Feature::WindowsSandbox);
}
self.windows_sandbox_mode
};
}
pub fn set_windows_elevated_sandbox_enabled(&mut self, value: bool) {
if value {
self.features.enable(Feature::WindowsSandboxElevated);
self.forced_auto_mode_downgraded_on_windows = false;
self.windows_sandbox_mode = if value {
Some(WindowsSandboxModeToml::Elevated)
} else if matches!(
self.windows_sandbox_mode,
Some(WindowsSandboxModeToml::Elevated)
) {
None
} else {
self.features.disable(Feature::WindowsSandboxElevated);
}
self.windows_sandbox_mode
};
}
}
@@ -2076,13 +2091,7 @@ network_access = false # This should be ignored.
&PathBuf::from("/tmp/test"),
None,
);
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::DangerFullAccess,
forced_auto_mode_downgraded_on_windows: false,
}
);
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
let sandbox_read_only = r#"
sandbox_mode = "read-only"
@@ -2101,13 +2110,7 @@ network_access = true # This should be ignored.
&PathBuf::from("/tmp/test"),
None,
);
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::ReadOnly,
forced_auto_mode_downgraded_on_windows: false,
}
);
assert_eq!(resolution, SandboxPolicy::ReadOnly);
let writable_root = test_absolute_path("/my/workspace");
let sandbox_workspace_write = format!(
@@ -2135,24 +2138,15 @@ exclude_slash_tmp = true
None,
);
if cfg!(target_os = "windows") {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::ReadOnly,
forced_auto_mode_downgraded_on_windows: true,
}
);
assert_eq!(resolution, SandboxPolicy::ReadOnly);
} else {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root.clone()],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
forced_auto_mode_downgraded_on_windows: false,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root.clone()],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
}
@@ -2185,24 +2179,15 @@ trust_level = "trusted"
None,
);
if cfg!(target_os = "windows") {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::ReadOnly,
forced_auto_mode_downgraded_on_windows: true,
}
);
assert_eq!(resolution, SandboxPolicy::ReadOnly);
} else {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
forced_auto_mode_downgraded_on_windows: false,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
}
@@ -2365,10 +2350,6 @@ trust_level = "trusted"
let expected_backend = AbsolutePathBuf::try_from(backend).unwrap();
if cfg!(target_os = "windows") {
assert!(
config.forced_auto_mode_downgraded_on_windows,
"expected workspace-write request to be downgraded on Windows"
);
match config.sandbox_policy.get() {
&SandboxPolicy::ReadOnly => {}
other => panic!("expected read-only policy on Windows, got {other:?}"),
@@ -2701,13 +2682,11 @@ profile = "project"
config.sandbox_policy.get(),
SandboxPolicy::ReadOnly
));
assert!(config.forced_auto_mode_downgraded_on_windows);
} else {
assert!(matches!(
config.sandbox_policy.get(),
SandboxPolicy::WorkspaceWrite { .. }
));
assert!(!config.forced_auto_mode_downgraded_on_windows);
}
Ok(())
@@ -4026,7 +4005,6 @@ model_verbosity = "high"
enforce_residency: Constrained::allow_any(None),
network: None,
did_user_set_custom_approval_policy_or_sandbox_mode: true,
forced_auto_mode_downgraded_on_windows: false,
shell_environment_policy: ShellEnvironmentPolicy::default(),
user_instructions: None,
notify: None,
@@ -4069,6 +4047,7 @@ model_verbosity = "high"
suppress_unstable_features_warning: false,
active_profile: Some("o3".to_string()),
active_project: ProjectConfig { trust_level: None },
windows_sandbox_mode: None,
windows_wsl_setup_acknowledged: false,
notices: Default::default(),
check_for_update_on_startup: true,
@@ -4132,7 +4111,6 @@ model_verbosity = "high"
enforce_residency: Constrained::allow_any(None),
network: None,
did_user_set_custom_approval_policy_or_sandbox_mode: true,
forced_auto_mode_downgraded_on_windows: false,
shell_environment_policy: ShellEnvironmentPolicy::default(),
user_instructions: None,
notify: None,
@@ -4175,6 +4153,7 @@ model_verbosity = "high"
suppress_unstable_features_warning: false,
active_profile: Some("gpt3".to_string()),
active_project: ProjectConfig { trust_level: None },
windows_sandbox_mode: None,
windows_wsl_setup_acknowledged: false,
notices: Default::default(),
check_for_update_on_startup: true,
@@ -4236,7 +4215,6 @@ model_verbosity = "high"
enforce_residency: Constrained::allow_any(None),
network: None,
did_user_set_custom_approval_policy_or_sandbox_mode: true,
forced_auto_mode_downgraded_on_windows: false,
shell_environment_policy: ShellEnvironmentPolicy::default(),
user_instructions: None,
notify: None,
@@ -4279,6 +4257,7 @@ model_verbosity = "high"
suppress_unstable_features_warning: false,
active_profile: Some("zdr".to_string()),
active_project: ProjectConfig { trust_level: None },
windows_sandbox_mode: None,
windows_wsl_setup_acknowledged: false,
notices: Default::default(),
check_for_update_on_startup: true,
@@ -4326,7 +4305,6 @@ model_verbosity = "high"
enforce_residency: Constrained::allow_any(None),
network: None,
did_user_set_custom_approval_policy_or_sandbox_mode: true,
forced_auto_mode_downgraded_on_windows: false,
shell_environment_policy: ShellEnvironmentPolicy::default(),
user_instructions: None,
notify: None,
@@ -4369,6 +4347,7 @@ model_verbosity = "high"
suppress_unstable_features_warning: false,
active_profile: Some("gpt5".to_string()),
active_project: ProjectConfig { trust_level: None },
windows_sandbox_mode: None,
windows_wsl_setup_acknowledged: false,
notices: Default::default(),
check_for_update_on_startup: true,
@@ -4671,15 +4650,13 @@ trust_level = "untrusted"
// Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)
if cfg!(target_os = "windows") {
assert!(
matches!(resolution.policy, SandboxPolicy::ReadOnly),
"Expected ReadOnly on Windows, got {:?}",
resolution.policy
matches!(resolution, SandboxPolicy::ReadOnly),
"Expected ReadOnly on Windows, got {resolution:?}"
);
} else {
assert!(
matches!(resolution.policy, SandboxPolicy::WorkspaceWrite { .. }),
"Expected WorkspaceWrite for untrusted project, got {:?}",
resolution.policy
matches!(resolution, SandboxPolicy::WorkspaceWrite { .. }),
"Expected WorkspaceWrite for untrusted project, got {resolution:?}"
);
}
@@ -4722,7 +4699,7 @@ trust_level = "untrusted"
Some(&constrained),
);
assert_eq!(resolution.policy, SandboxPolicy::DangerFullAccess);
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
Ok(())
}
@@ -4764,21 +4741,9 @@ trust_level = "untrusted"
);
if cfg!(target_os = "windows") {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::ReadOnly,
forced_auto_mode_downgraded_on_windows: true,
}
);
assert_eq!(resolution, SandboxPolicy::ReadOnly);
} else {
assert_eq!(
resolution,
SandboxPolicyResolution {
policy: SandboxPolicy::new_workspace_write_policy(),
forced_auto_mode_downgraded_on_windows: false,
}
);
assert_eq!(resolution, SandboxPolicy::new_workspace_write_policy());
}
Ok(())
}