diff --git a/codex-rs/core/src/safety.rs b/codex-rs/core/src/safety.rs index dec7a0087..4ed88783a 100644 --- a/codex-rs/core/src/safety.rs +++ b/codex-rs/core/src/safety.rs @@ -12,6 +12,11 @@ use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxType; use codex_sandboxing::get_platform_sandbox; +const PATCH_REJECTED_OUTSIDE_PROJECT_REASON: &str = + "writing outside of the project; rejected by user approval settings"; +const PATCH_REJECTED_READ_ONLY_REASON: &str = + "writing is blocked by read-only sandbox; rejected by user approval settings"; + #[derive(Debug, PartialEq)] pub enum SafetyCheck { AutoApprove { @@ -85,9 +90,7 @@ pub fn assess_patch_safety( None => { if rejects_sandbox_approval { SafetyCheck::Reject { - reason: - "writing outside of the project; rejected by user approval settings" - .to_string(), + reason: patch_rejection_reason(sandbox_policy).to_string(), } } else { SafetyCheck::AskUser @@ -97,14 +100,22 @@ pub fn assess_patch_safety( } } else if rejects_sandbox_approval { SafetyCheck::Reject { - reason: "writing outside of the project; rejected by user approval settings" - .to_string(), + reason: patch_rejection_reason(sandbox_policy).to_string(), } } else { SafetyCheck::AskUser } } +fn patch_rejection_reason(sandbox_policy: &SandboxPolicy) -> &'static str { + match sandbox_policy { + SandboxPolicy::ReadOnly { .. } => PATCH_REJECTED_READ_ONLY_REASON, + SandboxPolicy::WorkspaceWrite { .. } + | SandboxPolicy::DangerFullAccess + | SandboxPolicy::ExternalSandbox { .. } => PATCH_REJECTED_OUTSIDE_PROJECT_REASON, + } +} + fn is_write_patch_constrained_to_writable_paths( action: &ApplyPatchAction, file_system_sandbox_policy: &FileSystemSandboxPolicy, diff --git a/codex-rs/core/src/safety_tests.rs b/codex-rs/core/src/safety_tests.rs index e7f55693d..5ebeb64e0 100644 --- a/codex-rs/core/src/safety_tests.rs +++ b/codex-rs/core/src/safety_tests.rs @@ -162,8 +162,36 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() { WindowsSandboxLevel::Disabled, ), SafetyCheck::Reject { - reason: "writing outside of the project; rejected by user approval settings" - .to_string(), + reason: PATCH_REJECTED_OUTSIDE_PROJECT_REASON.to_string(), + }, + ); +} + +#[test] +fn read_only_policy_rejects_patch_with_read_only_reason() { + let tmp = TempDir::new().unwrap(); + let cwd = tmp.path().to_path_buf(); + let action = ApplyPatchAction::new_add_for_test(&cwd.join("inside.txt"), "".to_string()); + let sandbox_policy = SandboxPolicy::new_read_only_policy(); + let file_system_sandbox_policy = + FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, &cwd); + + assert!(!is_write_patch_constrained_to_writable_paths( + &action, + &file_system_sandbox_policy, + &cwd, + )); + assert_eq!( + assess_patch_safety( + &action, + AskForApproval::Never, + &sandbox_policy, + &file_system_sandbox_policy, + &cwd, + WindowsSandboxLevel::Disabled, + ), + SafetyCheck::Reject { + reason: PATCH_REJECTED_READ_ONLY_REASON.to_string(), }, ); }