mirror of
https://github.com/openai/codex.git
synced 2026-04-30 03:12:20 +03:00
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:
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user