From 1cb5f3205150d07ad0a558f378fb448b67782b00 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 2 Jun 2026 14:31:35 -0700 Subject: [PATCH 1/2] config: remove dead profile sandbox fallback --- codex-rs/config/src/config_toml.rs | 7 ++----- codex-rs/core/src/config/config_tests.rs | 11 +---------- codex-rs/core/src/config/mod.rs | 1 - 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/codex-rs/config/src/config_toml.rs b/codex-rs/config/src/config_toml.rs index 0765819fb7..0311482cf2 100644 --- a/codex-rs/config/src/config_toml.rs +++ b/codex-rs/config/src/config_toml.rs @@ -724,16 +724,13 @@ impl ConfigToml { pub async fn derive_permission_profile( &self, sandbox_mode_override: Option, - profile_sandbox_mode: Option, windows_sandbox_level: WindowsSandboxLevel, active_project: Option<&ProjectConfig>, permission_profile_constraint: Option<&crate::Constrained>, ) -> PermissionProfile { - let sandbox_mode_was_explicit = sandbox_mode_override.is_some() - || profile_sandbox_mode.is_some() - || self.sandbox_mode.is_some(); + let sandbox_mode_was_explicit = + sandbox_mode_override.is_some() || self.sandbox_mode.is_some(); let resolved_sandbox_mode = sandbox_mode_override - .or(profile_sandbox_mode) .or(self.sandbox_mode) .or(if sandbox_mode_was_explicit { None diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 795057963e..a8531f06d2 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -165,7 +165,6 @@ fn http_mcp(url: &str) -> McpServerConfig { async fn derive_legacy_sandbox_policy_for_test( cfg: &ConfigToml, sandbox_mode_override: Option, - profile_sandbox_mode: Option, windows_sandbox_level: WindowsSandboxLevel, active_project: Option<&ProjectConfig>, permission_profile_constraint: Option<&Constrained>, @@ -173,7 +172,6 @@ async fn derive_legacy_sandbox_policy_for_test( let permission_profile = cfg .derive_permission_profile( sandbox_mode_override, - profile_sandbox_mode, windows_sandbox_level, active_project, permission_profile_constraint, @@ -3530,7 +3528,6 @@ network_access = false # This should be ignored. let resolution = derive_legacy_sandbox_policy_for_test( &sandbox_full_access_cfg, sandbox_mode_override, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, /*active_project*/ None, /*permission_profile_constraint*/ None, @@ -3551,7 +3548,6 @@ network_access = true # This should be ignored. let resolution = derive_legacy_sandbox_policy_for_test( &sandbox_read_only_cfg, sandbox_mode_override, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, /*active_project*/ None, /*permission_profile_constraint*/ None, @@ -3583,7 +3579,6 @@ trust_level = "trusted" let resolution = derive_legacy_sandbox_policy_for_test( &sandbox_workspace_write_cfg, sandbox_mode_override, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, /*active_project*/ None, /*permission_profile_constraint*/ None, @@ -3623,7 +3618,6 @@ exclude_slash_tmp = true let resolution = derive_legacy_sandbox_policy_for_test( &sandbox_workspace_write_cfg, sandbox_mode_override, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, /*active_project*/ None, /*permission_profile_constraint*/ None, @@ -4999,7 +4993,7 @@ model = "gpt-project-local" } #[tokio::test] -async fn unselected_profile_sandbox_mode_is_ignored() -> std::io::Result<()> { +async fn unselected_legacy_profile_sandbox_setting_is_ignored() -> std::io::Result<()> { let codex_home = TempDir::new()?; let mut profiles = HashMap::new(); profiles.insert( @@ -8587,7 +8581,6 @@ trust_level = "untrusted" let resolution = derive_legacy_sandbox_policy_for_test( &cfg, /*sandbox_mode_override*/ None, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, Some(&active_project), /*permission_profile_constraint*/ None, @@ -8644,7 +8637,6 @@ async fn derive_sandbox_policy_falls_back_to_read_only_for_implicit_defaults() - let resolution = derive_legacy_sandbox_policy_for_test( &cfg, /*sandbox_mode_override*/ None, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, Some(&active_project), Some(&constrained), @@ -8697,7 +8689,6 @@ async fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallb let resolution = derive_legacy_sandbox_policy_for_test( &cfg, /*sandbox_mode_override*/ None, - /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, Some(&active_project), Some(&constrained), diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 9506819d03..fd71cea987 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -2936,7 +2936,6 @@ impl Config { let mut permission_profile = cfg .derive_permission_profile( sandbox_mode, - /*profile_sandbox_mode*/ None, windows_sandbox_level, Some(&active_project), Some(&constrained_permission_profile), From 5750eb9fe4dd9a47a0d67ea0ac9840b8036efc84 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 2 Jun 2026 14:31:41 -0700 Subject: [PATCH 2/2] config: default untrusted projects to read-only permissions --- codex-rs/config/src/config_toml.rs | 6 +- .../core/src/config/config_loader_tests.rs | 6 +- codex-rs/core/src/config/config_tests.rs | 87 ++++++++++++------- codex-rs/core/src/config/permissions.rs | 2 +- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/codex-rs/config/src/config_toml.rs b/codex-rs/config/src/config_toml.rs index 0311482cf2..aee259e6fd 100644 --- a/codex-rs/config/src/config_toml.rs +++ b/codex-rs/config/src/config_toml.rs @@ -735,11 +735,11 @@ impl ConfigToml { .or(if sandbox_mode_was_explicit { None } else { - // If no sandbox_mode is set but this directory has a trust decision, - // default to workspace-write except on unsandboxed Windows where we + // If no sandbox_mode is set for a trusted directory, default + // to workspace-write except on unsandboxed Windows where we // default to read-only. active_project.and_then(|p| { - if p.is_trusted() || p.is_untrusted() { + if p.is_trusted() { if cfg!(target_os = "windows") && windows_sandbox_level == WindowsSandboxLevel::Disabled { diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 5edecebec0..01c23f5408 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -1423,11 +1423,7 @@ async fn system_allowed_permissions_keep_builtin_permission_fallbacks() -> anyho ), ( Some(TrustLevel::Untrusted), - if cfg!(target_os = "windows") { - BUILT_IN_PERMISSION_PROFILE_READ_ONLY - } else { - BUILT_IN_PERMISSION_PROFILE_WORKSPACE - }, + BUILT_IN_PERMISSION_PROFILE_READ_ONLY, ), (None, BUILT_IN_PERMISSION_PROFILE_READ_ONLY), ] { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index a8531f06d2..3e10f8d7ab 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -2549,6 +2549,50 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std:: Ok(()) } +#[tokio::test] +async fn empty_config_defaults_to_builtin_read_only_for_untrusted_project() -> std::io::Result<()> { + let codex_home = TempDir::new()?; + let cwd = TempDir::new()?; + let project_key = cwd.path().to_string_lossy().to_string(); + + let config = Config::load_from_base_config_with_overrides( + ConfigToml { + projects: Some(HashMap::from([( + project_key, + ProjectConfig { + trust_level: Some(TrustLevel::Untrusted), + }, + )])), + ..Default::default() + }, + ConfigOverrides { + cwd: Some(cwd.path().to_path_buf()), + ..Default::default() + }, + codex_home.abs(), + ) + .await?; + + let policy = config.permissions.file_system_sandbox_policy(); + assert_eq!( + config + .permissions + .active_permission_profile() + .as_ref() + .map(|active| active.id.as_str()), + Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY) + ); + assert!( + policy.can_read_path_with_cwd(cwd.path(), cwd.path()), + "expected untrusted project fallback to allow reads, policy: {policy:?}" + ); + assert!( + !policy.can_write_path_with_cwd(cwd.path(), cwd.path()), + "expected untrusted project fallback to use :read-only, policy: {policy:?}" + ); + Ok(()) +} + #[tokio::test] async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_settings() -> std::io::Result<()> { @@ -8566,7 +8610,7 @@ async fn test_load_config_rejects_legacy_ollama_chat_provider_with_helpful_error } #[tokio::test] -async fn test_untrusted_project_gets_workspace_write_sandbox() -> anyhow::Result<()> { +async fn test_untrusted_project_gets_read_only_sandbox() -> anyhow::Result<()> { let config_with_untrusted = r#" [projects."/tmp/test"] trust_level = "untrusted" @@ -8587,18 +8631,10 @@ trust_level = "untrusted" ) .await; - // Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade) - if cfg!(target_os = "windows") { - assert!( - matches!(resolution, SandboxPolicy::ReadOnly { .. }), - "Expected ReadOnly on Windows, got {resolution:?}" - ); - } else { - assert!( - matches!(resolution, SandboxPolicy::WorkspaceWrite { .. }), - "Expected WorkspaceWrite for untrusted project, got {resolution:?}" - ); - } + assert!( + matches!(resolution, SandboxPolicy::ReadOnly { .. }), + "Expected ReadOnly for untrusted project, got {resolution:?}" + ); Ok(()) } @@ -8955,24 +8991,13 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow: "Expected UnlessTrusted approval policy for untrusted project" ); - // Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows) - if cfg!(target_os = "windows") { - assert!( - matches!( - &config.legacy_sandbox_policy(), - SandboxPolicy::ReadOnly { .. } - ), - "Expected ReadOnly on Windows" - ); - } else { - assert!( - matches!( - &config.legacy_sandbox_policy(), - SandboxPolicy::WorkspaceWrite { .. } - ), - "Expected WorkspaceWrite sandbox for untrusted project" - ); - } + assert!( + matches!( + &config.legacy_sandbox_policy(), + SandboxPolicy::ReadOnly { .. } + ), + "Expected ReadOnly sandbox for untrusted project" + ); Ok(()) } diff --git a/codex-rs/core/src/config/permissions.rs b/codex-rs/core/src/config/permissions.rs index f683d9c7eb..f338d2d61d 100644 --- a/codex-rs/core/src/config/permissions.rs +++ b/codex-rs/core/src/config/permissions.rs @@ -49,7 +49,7 @@ pub(crate) fn default_builtin_permission_profile_name( active_project: &ProjectConfig, windows_sandbox_level: WindowsSandboxLevel, ) -> &'static str { - if (active_project.is_trusted() || active_project.is_untrusted()) + if active_project.is_trusted() && !(cfg!(target_os = "windows") && windows_sandbox_level == WindowsSandboxLevel::Disabled) { BUILT_IN_WORKSPACE_PROFILE