From 39f0abc0a7c0ed0e348a6843e9f0c7b76e2400bc Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Wed, 20 May 2026 15:37:41 -0700 Subject: [PATCH 1/3] fix(config): reject readonly never fallback Co-authored-by: Codex noreply@openai.com --- codex-rs/core/src/config/config_tests.rs | 32 ++++++++++++++++++++++++ codex-rs/core/src/config/mod.rs | 10 ++++++++ 2 files changed, 42 insertions(+) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 19fd762da916..588810952452 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9712,6 +9712,38 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s Ok(()) } +#[tokio::test] +async fn danger_full_access_with_never_is_rejected_when_requirements_force_read_only() +-> std::io::Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#"approval_policy = "never" +sandbox_mode = "danger-full-access" +"#, + )?; + + let err = ConfigBuilder::without_managed_config_for_tests() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new(async { + Ok(Some(codex_config::ConfigRequirementsToml { + allowed_sandbox_modes: Some(vec![codex_config::SandboxModeRequirement::ReadOnly]), + ..Default::default() + })) + })) + .build() + .await + .expect_err("requirements-constrained yolo should require sandbox approval"); + + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + assert_eq!( + err.to_string(), + "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Change `approval_policy` to allow approvals or choose an allowed sandbox mode." + ); + Ok(()) +} + #[tokio::test] async fn permission_profile_override_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 434969e2c5f4..305ecdc9ff83 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3304,6 +3304,16 @@ impl Config { &mut constrained_permission_profile, &mut startup_warnings, )?; + if permission_profile_was_constrained + && original_permission_profile == PermissionProfile::Disabled + && constrained_permission_profile.get() == &PermissionProfile::read_only() + && constrained_approval_policy.value() == AskForApproval::Never + { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Change `approval_policy` to allow approvals or choose an allowed sandbox mode.", + )); + } if permission_profile_was_constrained { // The selected profile no longer describes the effective // permissions after requirements forced a fallback. From f24c47bbaecefd8d3d1026e5cd2de9e650711174 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Wed, 20 May 2026 15:54:14 -0700 Subject: [PATCH 2/3] fix(config): clarify approval fallback guidance Co-authored-by: Codex noreply@openai.com --- codex-rs/core/src/config/config_tests.rs | 2 +- codex-rs/core/src/config/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 588810952452..6ae20e27b7e0 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9739,7 +9739,7 @@ sandbox_mode = "danger-full-access" assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert_eq!( err.to_string(), - "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Change `approval_policy` to allow approvals or choose an allowed sandbox mode." + "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Choose an `approval_policy` based on what you need, such as `on-request`, or choose an allowed sandbox mode." ); Ok(()) } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 305ecdc9ff83..eebfa25ef7ad 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3311,7 +3311,7 @@ impl Config { { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, - "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Change `approval_policy` to allow approvals or choose an allowed sandbox mode.", + "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Choose an `approval_policy` based on what you need, such as `on-request`, or choose an allowed sandbox mode.", )); } if permission_profile_was_constrained { From 0297a2de5d430286554df40cd13a645cc26fb851 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Wed, 20 May 2026 16:33:16 -0700 Subject: [PATCH 3/3] test(config): cover constrained full-access profiles Co-authored-by: Codex noreply@openai.com --- .../app-server/tests/suite/v2/turn_start.rs | 8 +++-- codex-rs/core/src/config/config_tests.rs | 35 +++++++++++++++++++ codex-rs/core/src/config/mod.rs | 3 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 66f704f83d91..236d57a0eee6 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -1004,10 +1004,14 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn() assert!( err.error .message - .contains("invalid thread settings override") + .contains("`approval_policy = \"never\"` cannot be used"), + "unexpected error message: {}", + err.error.message ); assert!( - err.error.message.contains("allowed set [ReadOnly]"), + err.error + .message + .contains("requirements do not allow `sandbox_mode = \"danger-full-access\"`"), "unexpected error message: {}", err.error.message ); diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 6ae20e27b7e0..b1ea90bec28a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9744,6 +9744,41 @@ sandbox_mode = "danger-full-access" Ok(()) } +#[tokio::test] +async fn named_full_access_profile_with_never_is_rejected_when_requirements_force_read_only() +-> std::io::Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#"approval_policy = "never" +default_permissions = "dev" + +[permissions.dev.filesystem] +":root" = "write" +"#, + )?; + + let err = ConfigBuilder::without_managed_config_for_tests() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new(async { + Ok(Some(codex_config::ConfigRequirementsToml { + allowed_sandbox_modes: Some(vec![codex_config::SandboxModeRequirement::ReadOnly]), + ..Default::default() + })) + })) + .build() + .await + .expect_err("requirements-constrained full-access profile should require sandbox approval"); + + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + assert_eq!( + err.to_string(), + "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Choose an `approval_policy` based on what you need, such as `on-request`, or choose an allowed sandbox mode." + ); + Ok(()) +} + #[tokio::test] async fn permission_profile_override_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index eebfa25ef7ad..d507dadb7f3f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3305,7 +3305,8 @@ impl Config { &mut startup_warnings, )?; if permission_profile_was_constrained - && original_permission_profile == PermissionProfile::Disabled + && sandbox_mode_requirement_for_permission_profile(&original_permission_profile) + == SandboxModeRequirement::DangerFullAccess && constrained_permission_profile.get() == &PermissionProfile::read_only() && constrained_approval_policy.value() == AskForApproval::Never {