diff --git a/codex-rs/app-server/src/request_processors/thread_summary.rs b/codex-rs/app-server/src/request_processors/thread_summary.rs index da7ae54804..a57f2fcf79 100644 --- a/codex-rs/app-server/src/request_processors/thread_summary.rs +++ b/codex-rs/app-server/src/request_processors/thread_summary.rs @@ -179,11 +179,8 @@ pub(crate) fn thread_response_sandbox_policy( permission_profile: &codex_protocol::models::PermissionProfile, cwd: &Path, ) -> codex_app_server_protocol::SandboxPolicy { - let file_system_policy = permission_profile.file_system_sandbox_policy(); let sandbox_policy = codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( permission_profile, - &file_system_policy, - permission_profile.network_sandbox_policy(), cwd, ); sandbox_policy.into() diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 25beee3cdb..eef7f4121c 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -524,7 +524,10 @@ impl TurnRequestProcessor { // `thread/settings/update` only acknowledges that the update was queued. // Clients that send dependent partial updates should wait for // `thread/settings/updated` or combine the fields in one request. - let snapshot = if permissions.is_some() || runtime_workspace_roots_request.is_some() { + let snapshot = if sandbox_policy.is_some() + || permissions.is_some() + || runtime_workspace_roots_request.is_some() + { Some(thread.config_snapshot().await) } else { None @@ -564,7 +567,7 @@ impl TurnRequestProcessor { let approvals_reviewer = approvals_reviewer.map(codex_app_server_protocol::ApprovalsReviewer::to_core); let sandbox_policy = sandbox_policy.map(|policy| policy.to_core()); - let (permission_profile, active_permission_profile, profile_workspace_roots) = + let (permission_profile, active_permission_profile, profile_workspace_roots) = { if let Some(permissions) = permissions { let Some(snapshot) = snapshot.as_ref() else { return Err(internal_error(format!( @@ -611,9 +614,32 @@ impl TurnRequestProcessor { config.permissions.active_permission_profile(), Some(config.permissions.profile_workspace_roots().to_vec()), ) + } else if let Some(sandbox_policy) = sandbox_policy.as_ref() { + let Some(snapshot) = snapshot.as_ref() else { + return Err(internal_error(format!( + "{method} sandbox policy missing thread snapshot" + ))); + }; + let sandbox_cwd = cwd + .as_ref() + .map(|cwd| { + AbsolutePathBuf::resolve_path_against_base(cwd, snapshot.cwd.as_path()) + }) + .unwrap_or_else(|| snapshot.cwd.clone()); + ( + Some( + codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy_for_cwd( + sandbox_policy, + sandbox_cwd.as_path(), + ), + ), + None, + None, + ) } else { (None, None, None) - }; + } + }; let effort = effort.map(Some); if has_any_overrides { @@ -623,7 +649,6 @@ impl TurnRequestProcessor { workspace_roots: runtime_workspace_roots.clone(), approval_policy, approvals_reviewer, - sandbox_policy: sandbox_policy.clone(), permission_profile: permission_profile.clone(), active_permission_profile: active_permission_profile.clone(), profile_workspace_roots: profile_workspace_roots.clone(), @@ -647,7 +672,7 @@ impl TurnRequestProcessor { profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, + sandbox_policy: None, permission_profile, active_permission_profile, windows_sandbox_level: None, diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index cd90829c0c..2ef53ee822 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -76,11 +76,8 @@ pub struct ThreadConfigSnapshot { impl ThreadConfigSnapshot { pub fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.permission_profile.file_system_sandbox_policy(); codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( &self.permission_profile, - &file_system_sandbox_policy, - self.permission_profile.network_sandbox_policy(), self.cwd.as_path(), ) } @@ -94,7 +91,6 @@ pub struct CodexThreadSettingsOverrides { pub profile_workspace_roots: Option>, pub approval_policy: Option, pub approvals_reviewer: Option, - pub sandbox_policy: Option, pub permission_profile: Option, pub active_permission_profile: Option, pub windows_sandbox_level: Option, @@ -321,7 +317,6 @@ impl CodexThread { profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, @@ -348,7 +343,6 @@ impl CodexThread { profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 43b2a2ed29..28e97c36a4 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -444,13 +444,7 @@ impl Permissions { /// Legacy compatibility projection derived from the canonical profile. pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy { let permission_profile = self.materialized_permission_profile(); - let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); - compatibility_sandbox_policy_for_permission_profile( - &permission_profile, - &file_system_sandbox_policy, - permission_profile.network_sandbox_policy(), - cwd, - ) + compatibility_sandbox_policy_for_permission_profile(&permission_profile, cwd) } /// Check whether a legacy sandbox policy can be applied to this permission diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index e050552246..8e87528df0 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -31,19 +31,18 @@ use codex_protocol::error::SandboxErr; use codex_protocol::exec_output::ExecToolCallOutput; use codex_protocol::exec_output::StreamOutput; use codex_protocol::models::PermissionProfile; -use codex_protocol::permissions::FileSystemSandboxKind; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecCommandOutputDeltaEvent; use codex_protocol::protocol::ExecOutputStream; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; use codex_sandboxing::SandboxablePreference; +use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP; use codex_utils_pty::process_group::kill_child_process_group; @@ -419,22 +418,17 @@ pub fn build_exec_request( exec_req.windows_sandbox_level, exec_req.network.is_some(), ); - let sandbox_policy = exec_req.compatibility_sandbox_policy(); exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend { resolve_windows_elevated_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, + &exec_req.permission_profile, sandbox_cwd, use_windows_elevated_backend, ) } else { resolve_windows_restricted_token_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, + &exec_req.permission_profile, sandbox_cwd, exec_req.windows_sandbox_level, ) @@ -1004,34 +998,28 @@ async fn exec( } #[cfg_attr(not(target_os = "windows"), allow(dead_code))] -fn should_use_windows_restricted_token_sandbox( - sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, +fn permission_profile_supports_windows_restricted_token_sandbox( + permission_profile: &PermissionProfile, ) -> bool { - sandbox == SandboxType::WindowsRestrictedToken - && file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted - && !matches!( - sandbox_policy, - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } - ) + match permission_profile { + PermissionProfile::Managed { file_system, .. } => { + !file_system.to_sandbox_policy().has_full_disk_write_access() + } + PermissionProfile::Disabled | PermissionProfile::External { .. } => false, + } } #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, windows_sandbox_level: WindowsSandboxLevel, ) -> Option { if windows_sandbox_level == WindowsSandboxLevel::Elevated { resolve_windows_elevated_filesystem_overrides( sandbox, - sandbox_policy, - file_system_sandbox_policy, - network_sandbox_policy, + permission_profile, sandbox_policy_cwd, windows_sandbox_level == WindowsSandboxLevel::Elevated, ) @@ -1039,9 +1027,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( } else { resolve_windows_restricted_token_filesystem_overrides( sandbox, - sandbox_policy, - file_system_sandbox_policy, - network_sandbox_policy, + permission_profile, sandbox_policy_cwd, windows_sandbox_level, ) @@ -1051,9 +1037,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, windows_sandbox_level: WindowsSandboxLevel, ) -> std::result::Result, String> { @@ -1063,25 +1047,22 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( return Ok(None); } + let (file_system_sandbox_policy, network_sandbox_policy) = + permission_profile.to_runtime_permissions(); + let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); - if should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) && !needs_direct_runtime_enforcement + if permission_profile_supports_windows_restricted_token_sandbox(permission_profile) + && !needs_direct_runtime_enforcement { return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + if !permission_profile_supports_windows_restricted_token_sandbox(permission_profile) { + let permission_profile_name = permission_profile_display_name(permission_profile); return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile_name}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } @@ -1090,7 +1071,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( // but its WRITE_RESTRICTED token does not make capability SID deny-read ACEs // participate in read access checks. Read restrictions therefore require the // elevated backend, even when the filesystem root remains readable. - if !windows_policy_has_root_read_access(file_system_sandbox_policy, sandbox_policy_cwd) { + if !windows_policy_has_root_read_access(&file_system_sandbox_policy, sandbox_policy_cwd) { return Err( "windows unelevated restricted-token sandbox cannot enforce split filesystem read restrictions directly; refusing to run unsandboxed" .to_string(), @@ -1098,7 +1079,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( } let additional_deny_read_paths = codex_windows_sandbox::resolve_windows_deny_read_paths( - file_system_sandbox_policy, + &file_system_sandbox_policy, sandbox_policy_cwd, )?; if !additional_deny_read_paths.is_empty() { @@ -1108,7 +1089,11 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( ); } - let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); + let legacy_projection = compatibility_sandbox_policy_for_permission_profile( + permission_profile, + sandbox_policy_cwd.as_path(), + ); + let legacy_writable_roots = legacy_projection.get_writable_roots_with_cwd(sandbox_policy_cwd); let split_writable_roots = file_system_sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); let legacy_root_paths: BTreeSet = legacy_writable_roots @@ -1204,9 +1189,7 @@ fn windows_policy_has_root_read_access( pub(crate) fn resolve_windows_elevated_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, use_windows_elevated_backend: bool, ) -> std::result::Result, String> { @@ -1214,19 +1197,19 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + let (file_system_sandbox_policy, network_sandbox_policy) = + permission_profile.to_runtime_permissions(); + + if !permission_profile_supports_windows_restricted_token_sandbox(permission_profile) { + let permission_profile_name = permission_profile_display_name(permission_profile); return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile_name}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } let additional_deny_read_paths = codex_windows_sandbox::resolve_windows_deny_read_paths( - file_system_sandbox_policy, + &file_system_sandbox_policy, sandbox_policy_cwd, )?; @@ -1242,7 +1225,11 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path); - let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); + let legacy_projection = compatibility_sandbox_policy_for_permission_profile( + permission_profile, + sandbox_policy_cwd.as_path(), + ); + let legacy_writable_roots = legacy_projection.get_writable_roots_with_cwd(sandbox_policy_cwd); let legacy_root_paths: BTreeSet = legacy_writable_roots .iter() .map(|root| normalize_path(root.root.to_path_buf())) @@ -1264,7 +1251,7 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( // whether the baseline still reads from the filesystem root and only needs // additional deny ACLs layered on top. let split_has_root_read_access = - windows_policy_has_root_read_access(file_system_sandbox_policy, sandbox_policy_cwd); + windows_policy_has_root_read_access(&file_system_sandbox_policy, sandbox_policy_cwd); let read_roots_override = if split_has_root_read_access { None } else { @@ -1329,6 +1316,14 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( })) } +fn permission_profile_display_name(permission_profile: &PermissionProfile) -> &'static str { + match permission_profile { + PermissionProfile::Managed { .. } => "Managed", + PermissionProfile::Disabled => "Disabled", + PermissionProfile::External { .. } => "External", + } +} + fn has_reopened_writable_descendant( writable_roots: &[codex_protocol::protocol::WritableRoot], ) -> bool { diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 1c71d02651..84dfaba5d7 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -379,34 +379,18 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result #[test] fn windows_restricted_token_skips_external_sandbox_policies() { - let policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, + let permission_profile = PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }; - let file_system_policy = FileSystemSandboxPolicy::from(&policy); - assert_eq!( - should_use_windows_restricted_token_sandbox( - SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - ), - false - ); + assert!(!permission_profile_supports_windows_restricted_token_sandbox(&permission_profile)); } #[test] -fn windows_restricted_token_runs_for_legacy_restricted_policies() { - let policy = SandboxPolicy::new_read_only_policy(); - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_supports_read_only_profiles() { + let permission_profile = PermissionProfile::read_only(); - assert_eq!( - should_use_windows_restricted_token_sandbox( - SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - ), - true - ); + assert!(permission_profile_supports_windows_restricted_token_sandbox(&permission_profile)); } #[test] @@ -427,39 +411,64 @@ fn windows_proxy_enforcement_uses_elevated_backend() { #[test] fn windows_restricted_token_rejects_network_only_restrictions() { - let policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, - }; - let file_system_policy = FileSystemSandboxPolicy::unrestricted(); + let permission_profile = PermissionProfile::from_runtime_permissions( + &FileSystemSandboxPolicy::unrestricted(), + NetworkSandboxPolicy::Restricted, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), Some( - "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string() + "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, permission_profile=Managed; refusing to run unsandboxed".to_string() ) ); } #[test] -fn windows_restricted_token_allows_legacy_restricted_policies() { - let policy = SandboxPolicy::new_read_only_policy(); - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_rejects_managed_root_write_profiles() { + let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ + codex_protocol::permissions::FileSystemSandboxEntry { + path: codex_protocol::permissions::FileSystemPath::Special { + value: codex_protocol::permissions::FileSystemSpecialPath::Root, + }, + access: codex_protocol::permissions::FileSystemAccessMode::Write, + }, + ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, + &sandbox_policy_cwd, + WindowsSandboxLevel::RestrictedToken, + ), + Some( + "windows sandbox backend cannot enforce file_system=Restricted, network=Restricted, permission_profile=Managed; refusing to run unsandboxed" + .to_string() + ) + ); +} + +#[test] +fn windows_restricted_token_allows_read_only_profiles() { + let permission_profile = PermissionProfile::read_only(); + let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); + + assert_eq!( + unsupported_windows_restricted_token_sandbox_reason( + SandboxType::WindowsRestrictedToken, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -468,22 +477,19 @@ fn windows_restricted_token_allows_legacy_restricted_policies() { } #[test] -fn windows_restricted_token_allows_legacy_workspace_write_policies() { - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_allows_workspace_write_profiles() { + let permission_profile = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -499,22 +505,21 @@ fn windows_elevated_allows_split_restricted_read_policies() { ) .expect("absolute docs"); std::fs::create_dir_all(docs.as_path()).expect("create docs"); - let policy = SandboxPolicy::ReadOnly { - network_access: false, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Path { path: docs }, access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::Elevated, ), @@ -527,12 +532,6 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -550,13 +549,15 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::RestrictedToken, ), @@ -572,12 +573,6 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -593,13 +588,15 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::RestrictedToken, ), @@ -618,12 +615,6 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { .abs(); let docs = cwd.join("docs"); std::fs::create_dir_all(docs.as_path()).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -644,17 +635,20 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); - // The legacy workspace-write root already protects top-level `.codex`, so - // the restricted-token overlay only needs the extra read-only docs carveout. + // The workspace-write compatibility projection already protects top-level + // `.codex`, so the restricted-token overlay only needs the extra read-only + // docs carveout. let expected_deny_write_paths = vec![docs]; assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -676,12 +670,6 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() { .abs(); let blocked = cwd.join("blocked"); std::fs::create_dir_all(blocked.as_path()).expect("create blocked"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -702,13 +690,15 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -725,9 +715,6 @@ fn windows_elevated_supports_split_restricted_read_roots() { let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); let expected_docs = dunce::canonicalize(&docs).expect("canonical docs"); - let policy = SandboxPolicy::ReadOnly { - network_access: false, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Path { @@ -737,13 +724,15 @@ fn windows_elevated_supports_split_restricted_read_roots() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -763,12 +752,6 @@ fn windows_elevated_supports_split_write_read_carveouts() { let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); let expected_docs = dunce::canonicalize(&docs).expect("canonical docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -792,13 +775,15 @@ fn windows_elevated_supports_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -821,12 +806,6 @@ fn windows_elevated_supports_unreadable_split_carveouts() { let blocked = temp_dir.path().join("blocked"); std::fs::create_dir_all(&blocked).expect("create blocked"); let expected_blocked = dunce::canonicalize(&blocked).expect("canonical blocked"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -850,13 +829,15 @@ fn windows_elevated_supports_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -884,12 +865,6 @@ fn windows_elevated_supports_unreadable_globs() { let secret = temp_dir.path().join("app").join(".env"); std::fs::create_dir_all(secret.parent().expect("parent")).expect("create parent"); std::fs::write(&secret, "secret").expect("write secret"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -912,13 +887,15 @@ fn windows_elevated_supports_unreadable_globs() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -941,12 +918,6 @@ fn windows_elevated_rejects_reopened_writable_descendants() { let docs = temp_dir.path().join("docs"); let nested = docs.join("nested"); std::fs::create_dir_all(&nested).expect("create nested"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -977,13 +948,15 @@ fn windows_elevated_rejects_reopened_writable_descendants() { access: codex_protocol::permissions::FileSystemAccessMode::Write, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::Elevated, ), diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index f853ea3ba6..8a2a2849fd 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -22,10 +22,8 @@ use codex_protocol::models::PermissionProfile; pub use codex_protocol::models::SandboxPermissions; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxExecRequest; use codex_sandboxing::SandboxType; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::HashMap; @@ -102,15 +100,6 @@ impl ExecRequest { } } - pub(crate) fn compatibility_sandbox_policy(&self) -> SandboxPolicy { - compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - &self.file_system_sandbox_policy, - self.network_sandbox_policy, - self.windows_sandbox_policy_cwd.as_path(), - ) - } - pub(crate) fn from_sandbox_exec_request( request: SandboxExecRequest, options: ExecOptions, diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index 7730a30ad7..57e2b8c2b5 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -26,6 +26,7 @@ use crate::tasks::UserShellCommandMode; use crate::tasks::UserShellCommandTask; use crate::tasks::execute_user_shell_command; use codex_protocol::models::ContentItem; +use codex_protocol::models::PermissionProfile; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::CodexErrorInfo; @@ -138,6 +139,24 @@ async fn thread_settings_update( collaboration_mode, personality, } = thread_settings; + let (permission_profile, active_permission_profile) = + if let Some(permission_profile) = permission_profile { + (Some(permission_profile), active_permission_profile) + } else if let Some(sandbox_policy) = sandbox_policy { + let state = sess.state.lock().await; + let update_cwd = state + .session_configuration + .resolved_cwd_for_update(cwd.as_ref()); + ( + Some(PermissionProfile::from_legacy_sandbox_policy_for_cwd( + &sandbox_policy, + update_cwd.as_path(), + )), + None, + ) + } else { + (None, active_permission_profile) + }; let collaboration_mode = match collaboration_mode { Some(collaboration_mode) => collaboration_mode, None => { @@ -156,7 +175,6 @@ async fn thread_settings_update( profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 6ab7e86f32..15c56182fe 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -150,17 +150,11 @@ impl SessionConfiguration { } pub(super) fn sandbox_policy(&self) -> SandboxPolicy { - self.permission_profile() - .to_legacy_sandbox_policy(&self.cwd) - .unwrap_or_else(|_| { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - self.permission_profile_state.permission_profile(), - &file_system_sandbox_policy, - self.network_sandbox_policy(), - &self.cwd, - ) - }) + let permission_profile = self.permission_profile(); + codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( + &permission_profile, + &self.cwd, + ) } pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { @@ -254,19 +248,7 @@ impl SessionConfiguration { next_configuration.windows_sandbox_level = windows_sandbox_level; } - let absolute_cwd = updates - .cwd - .as_ref() - .map(|cwd| { - AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir( - cwd.as_path(), - )) - .unwrap_or_else(|e| { - warn!("failed to normalize update cwd: {cwd:?}: {e}"); - self.cwd.clone() - }) - }) - .unwrap_or_else(|| self.cwd.clone()); + let absolute_cwd = self.resolved_cwd_for_update(updates.cwd.as_ref()); let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path(); next_configuration.cwd = absolute_cwd; @@ -330,23 +312,6 @@ impl SessionConfiguration { )?; next_configuration.original_config_do_not_use = Arc::new(config); } - } else if let Some(sandbox_policy) = updates.sandbox_policy.clone() { - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( - &sandbox_policy, - &next_configuration.cwd, - ¤t_file_system_sandbox_policy, - ); - let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); - next_configuration - .permission_profile_state - .set_legacy_permission_profile( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ), - )?; } else if cwd_changed && file_system_policy_matches_legacy && file_system_policy_has_rebindable_project_root_write @@ -379,6 +344,17 @@ impl SessionConfiguration { Ok(next_configuration) } + pub(crate) fn resolved_cwd_for_update(&self, cwd: Option<&PathBuf>) -> AbsolutePathBuf { + cwd.map(|cwd| { + AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(cwd.as_path())) + .unwrap_or_else(|e| { + warn!("failed to normalize update cwd: {cwd:?}: {e}"); + self.cwd.clone() + }) + }) + .unwrap_or_else(|| self.cwd.clone()) + } + fn set_permission_profile_projection( &mut self, permission_profile: PermissionProfile, @@ -423,7 +399,6 @@ pub(crate) struct SessionSettingsUpdate { pub(crate) profile_workspace_roots: Option>, pub(crate) approval_policy: Option, pub(crate) approvals_reviewer: Option, - pub(crate) sandbox_policy: Option, pub(crate) permission_profile: Option, pub(crate) active_permission_profile: Option, pub(crate) windows_sandbox_level: Option, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 4d4709e65d..c6c22a73d7 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -916,9 +916,9 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow session .new_turn_with_sub_id( - "sandbox-policy-change".to_string(), + "permission-profile-change".to_string(), SessionSettingsUpdate { - sandbox_policy: Some(SandboxPolicy::DangerFullAccess), + permission_profile: Some(PermissionProfile::Disabled), ..Default::default() }, ) diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index d600368137..ff36796348 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -124,12 +124,8 @@ impl TurnContext { } pub(crate) fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - let network_sandbox_policy = self.network_sandbox_policy(); compatibility_sandbox_policy_for_permission_profile( &self.permission_profile, - &file_system_sandbox_policy, - network_sandbox_policy, #[allow(deprecated)] &self.cwd, ) diff --git a/codex-rs/core/src/spawn.rs b/codex-rs/core/src/spawn.rs index a2c4ebe597..a23a1d749e 100644 --- a/codex-rs/core/src/spawn.rs +++ b/codex-rs/core/src/spawn.rs @@ -30,7 +30,7 @@ pub enum StdioPolicy { Inherit, } -/// Spawns the appropriate child process for the ExecParams and SandboxPolicy, +/// Spawns the appropriate child process for the exec params and sandbox settings, /// ensuring the args and environment variables used to create the `Command` /// (and `Child`) honor the configuration. /// diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index bd8f6d9b98..39ef358f8c 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -290,19 +290,18 @@ impl SandboxManager { pub fn compatibility_sandbox_policy_for_permission_profile( permissions: &PermissionProfile, - file_system_policy: &FileSystemSandboxPolicy, - network_policy: NetworkSandboxPolicy, cwd: &Path, ) -> SandboxPolicy { permissions .to_legacy_sandbox_policy(cwd) .unwrap_or_else(|_| { + let (file_system_policy, network_policy) = permissions.to_runtime_permissions(); compatibility_workspace_write_policy(file_system_policy, network_policy, cwd) }) } fn compatibility_workspace_write_policy( - file_system_policy: &FileSystemSandboxPolicy, + file_system_policy: FileSystemSandboxPolicy, network_policy: NetworkSandboxPolicy, cwd: &Path, ) -> SandboxPolicy {