diff --git a/codex-rs/windows-sandbox-rs/src/audit.rs b/codex-rs/windows-sandbox-rs/src/audit.rs index c7e7fc0b11..cab0a06adf 100644 --- a/codex-rs/windows-sandbox-rs/src/audit.rs +++ b/codex-rs/windows-sandbox-rs/src/audit.rs @@ -8,7 +8,8 @@ use crate::logging::debug_log; use crate::logging::log_note; use crate::path_normalization::canonical_path_key; use crate::policy::SandboxPolicy; -use crate::setup::effective_write_roots_for_setup; +use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; +use crate::setup::effective_write_roots_for_permissions; use crate::token::LocalSid; use crate::token::world_sid; use anyhow::Result; @@ -259,11 +260,18 @@ pub fn apply_capability_denies_for_world_writable( let cap_path = cap_sid_file(codex_home); let caps = load_or_create_cap_sids(codex_home)?; std::fs::write(&cap_path, serde_json::to_string(&caps)?)?; - let (active_sids, workspace_roots): (Vec, Vec) = match sandbox_policy { - SandboxPolicy::WorkspaceWrite { .. } => { - let roots = effective_write_roots_for_setup( - sandbox_policy, - cwd, + if matches!( + sandbox_policy, + SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } + ) { + return Ok(()); + } + let permissions = + ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(sandbox_policy, cwd); + let (active_sids, workspace_roots): (Vec, Vec) = + if permissions.uses_write_capabilities_for_cwd(cwd, env_map) { + let roots = effective_write_roots_for_permissions( + &permissions, cwd, env_map, codex_home, @@ -277,14 +285,9 @@ pub fn apply_capability_denies_for_world_writable( }) .collect::>>()?; (active_sids, roots) - } - SandboxPolicy::ReadOnly { .. } => { + } else { (vec![LocalSid::from_string(&caps.readonly)?], Vec::new()) - } - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { - return Ok(()); - } - }; + }; for path in flagged { if workspace_roots .iter() diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 5861317376..0acbcceac4 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -39,10 +39,11 @@ mod windows_impl { use crate::logging::log_success; use crate::policy::SandboxPolicy; use crate::policy::parse_policy; + use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::runner_client::spawn_runner_transport; use crate::sandbox_utils::ensure_codex_home_exists; use crate::sandbox_utils::inject_git_safe_directory; - use crate::setup::effective_write_roots_for_setup; + use crate::setup::effective_write_roots_for_permissions; use crate::token::LocalSid; use anyhow::Result; use codex_protocol::models::PermissionProfile; @@ -81,6 +82,8 @@ mod windows_impl { .map(AbsolutePathBuf::to_path_buf) .collect::>(); let policy = parse_policy(policy_json_or_preset)?; + let permissions = + ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(&policy, cwd); normalize_null_device_env(&mut env_map); ensure_non_interactive_pager(&mut env_map); inherit_path_env(&mut env_map); @@ -112,32 +115,26 @@ mod windows_impl { anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing") } let caps = load_or_create_cap_sids(codex_home)?; - let (sid_for_null, cap_sids) = match &policy { - SandboxPolicy::ReadOnly { .. } => { - let sid = LocalSid::from_string(&caps.readonly)?; - (sid, vec![caps.readonly]) - } - SandboxPolicy::WorkspaceWrite { .. } => { - let write_roots = effective_write_roots_for_setup( - &policy, - sandbox_policy_cwd, - cwd, - &env_map, - codex_home, - write_roots_override, - ); - let cap_sids = write_roots - .iter() - .map(|root| workspace_write_cap_sid_for_root(codex_home, cwd, root)) - .collect::>>()?; - if cap_sids.is_empty() { - anyhow::bail!("workspace-write sandbox has no writable root capability SIDs"); - } - (LocalSid::from_string(&cap_sids[0])?, cap_sids) - } - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { - unreachable!("DangerFullAccess handled above") + let (sid_for_null, cap_sids) = if permissions.uses_write_capabilities_for_cwd(cwd, &env_map) + { + let write_roots = effective_write_roots_for_permissions( + &permissions, + cwd, + &env_map, + codex_home, + write_roots_override, + ); + let cap_sids = write_roots + .iter() + .map(|root| workspace_write_cap_sid_for_root(codex_home, cwd, root)) + .collect::>>()?; + if cap_sids.is_empty() { + anyhow::bail!("workspace-write sandbox has no writable root capability SIDs"); } + (LocalSid::from_string(&cap_sids[0])?, cap_sids) + } else { + let sid = LocalSid::from_string(&caps.readonly)?; + (sid, vec![caps.readonly]) }; unsafe { diff --git a/codex-rs/windows-sandbox-rs/src/identity.rs b/codex-rs/windows-sandbox-rs/src/identity.rs index e49af3017b..1ba08d8aed 100644 --- a/codex-rs/windows-sandbox-rs/src/identity.rs +++ b/codex-rs/windows-sandbox-rs/src/identity.rs @@ -1,12 +1,13 @@ use crate::dpapi; use crate::logging::debug_log; use crate::policy::SandboxPolicy; +use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::setup::SandboxNetworkIdentity; use crate::setup::SandboxUserRecord; use crate::setup::SandboxUsersFile; use crate::setup::SetupMarker; use crate::setup::gather_read_roots; -use crate::setup::gather_write_roots; +use crate::setup::gather_write_roots_for_permissions; use crate::setup::offline_proxy_settings_from_env; use crate::setup::run_elevated_setup; use crate::setup::run_setup_refresh_with_overrides; @@ -143,14 +144,16 @@ pub fn require_logon_sandbox_creds( deny_write_paths_override: &[PathBuf], proxy_enforced: bool, ) -> Result { + let permissions = + ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(policy, command_cwd); let sandbox_dir = crate::setup::sandbox_dir(codex_home); let needed_read = read_roots_override .map(<[PathBuf]>::to_vec) .unwrap_or_else(|| gather_read_roots(command_cwd, policy, codex_home)); let needed_write = write_roots_override .map(<[PathBuf]>::to_vec) - .unwrap_or_else(|| gather_write_roots(policy, policy_cwd, command_cwd, env_map)); - let network_identity = SandboxNetworkIdentity::from_policy(policy, proxy_enforced); + .unwrap_or_else(|| gather_write_roots_for_permissions(&permissions, command_cwd, env_map)); + let network_identity = SandboxNetworkIdentity::from_permissions(&permissions, proxy_enforced); let desired_offline_proxy_settings = offline_proxy_settings_from_env(env_map, network_identity); // NOTE: Do not add CODEX_HOME/.sandbox to `needed_write`; it must remain non-writable by the // restricted capability token. The setup helper's `lock_sandbox_dir` is responsible for diff --git a/codex-rs/windows-sandbox-rs/src/resolved_permissions.rs b/codex-rs/windows-sandbox-rs/src/resolved_permissions.rs index cbf4e22949..8a2fab650c 100644 --- a/codex-rs/windows-sandbox-rs/src/resolved_permissions.rs +++ b/codex-rs/windows-sandbox-rs/src/resolved_permissions.rs @@ -94,6 +94,18 @@ impl ResolvedWindowsSandboxPermissions { !self.network.is_enabled() } + pub(crate) fn network_policy(&self) -> NetworkSandboxPolicy { + self.network + } + + pub(crate) fn uses_write_capabilities_for_cwd( + &self, + cwd: &Path, + env_map: &HashMap, + ) -> bool { + !self.writable_roots_for_cwd(cwd, env_map).is_empty() + } + pub(crate) fn writable_roots_for_cwd( &self, cwd: &Path, diff --git a/codex-rs/windows-sandbox-rs/src/setup.rs b/codex-rs/windows-sandbox-rs/src/setup.rs index 6f4dd86019..80646c3e6f 100644 --- a/codex-rs/windows-sandbox-rs/src/setup.rs +++ b/codex-rs/windows-sandbox-rs/src/setup.rs @@ -17,6 +17,7 @@ use crate::logging::log_note; use crate::path_normalization::canonical_path_key; use crate::path_normalization::canonicalize_path; use crate::policy::SandboxPolicy; +use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::setup_error::SetupErrorCode; use crate::setup_error::SetupFailure; use crate::setup_error::clear_setup_error_report; @@ -173,8 +174,12 @@ fn run_setup_refresh_inner( let (read_roots, write_roots) = build_payload_roots(&request, &overrides); let deny_read_paths = build_payload_deny_read_paths(overrides.deny_read_paths); let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths); + let permissions = ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd( + request.policy, + request.command_cwd, + ); let network_identity = - SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced); + SandboxNetworkIdentity::from_permissions(&permissions, request.proxy_enforced); let offline_proxy_settings = offline_proxy_settings_from_env(request.env_map, network_identity); let payload = ElevationPayload { version: SETUP_VERSION, @@ -389,15 +394,16 @@ pub(crate) fn gather_read_roots( gather_legacy_full_read_roots(command_cwd, policy, codex_home) } -pub(crate) fn gather_write_roots( - policy: &SandboxPolicy, - policy_cwd: &Path, +pub(crate) fn gather_write_roots_for_permissions( + permissions: &ResolvedWindowsSandboxPermissions, command_cwd: &Path, env_map: &HashMap, ) -> Vec { - let AllowDenyPaths { allow, .. } = - compute_allow_paths(policy, policy_cwd, command_cwd, env_map); - let roots: Vec = allow.into_iter().collect(); + let roots = permissions + .writable_roots_for_cwd(command_cwd, env_map) + .into_iter() + .map(|root| root.root) + .collect::>(); let mut dedup: HashSet = HashSet::new(); let mut out: Vec = Vec::new(); for r in canonical_existing(&roots) { @@ -410,7 +416,25 @@ pub(crate) fn gather_write_roots( pub(crate) fn effective_write_roots_for_setup( policy: &SandboxPolicy, - policy_cwd: &Path, + _policy_cwd: &Path, + command_cwd: &Path, + env_map: &HashMap, + codex_home: &Path, + write_roots_override: Option<&[PathBuf]>, +) -> Vec { + let permissions = + ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(policy, command_cwd); + effective_write_roots_for_permissions( + &permissions, + command_cwd, + env_map, + codex_home, + write_roots_override, + ) +} + +pub(crate) fn effective_write_roots_for_permissions( + permissions: &ResolvedWindowsSandboxPermissions, command_cwd: &Path, env_map: &HashMap, codex_home: &Path, @@ -419,7 +443,7 @@ pub(crate) fn effective_write_roots_for_setup( let write_roots = if let Some(roots) = write_roots_override { canonical_existing(roots) } else { - gather_write_roots(policy, policy_cwd, command_cwd, env_map) + gather_write_roots_for_permissions(permissions, command_cwd, env_map) }; let write_roots = expand_user_profile_root(write_roots); let write_roots = filter_user_profile_root(write_roots); @@ -463,8 +487,11 @@ pub(crate) enum SandboxNetworkIdentity { } impl SandboxNetworkIdentity { - pub(crate) fn from_policy(policy: &SandboxPolicy, proxy_enforced: bool) -> Self { - if proxy_enforced || !policy.has_full_network_access() { + pub(crate) fn from_permissions( + permissions: &ResolvedWindowsSandboxPermissions, + proxy_enforced: bool, + ) -> Self { + if proxy_enforced || !permissions.network_policy().is_enabled() { Self::Offline } else { Self::Online @@ -744,8 +771,12 @@ pub fn run_elevated_setup( let (read_roots, write_roots) = build_payload_roots(&request, &overrides); let deny_read_paths = build_payload_deny_read_paths(overrides.deny_read_paths); let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths); + let permissions = ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd( + request.policy, + request.command_cwd, + ); let network_identity = - SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced); + SandboxNetworkIdentity::from_permissions(&permissions, request.proxy_enforced); let offline_proxy_settings = offline_proxy_settings_from_env(request.env_map, network_identity); let payload = ElevationPayload { version: SETUP_VERSION, diff --git a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs index 07881b54d4..aff92be85c 100644 --- a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs +++ b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs @@ -3,6 +3,7 @@ use crate::acl::add_deny_write_ace; use crate::acl::allow_null_device; use crate::allow::AllowDenyPaths; use crate::allow::compute_allow_paths; +use crate::allow::compute_allow_paths_for_permissions; use crate::cap::load_or_create_cap_sids; use crate::cap::workspace_write_cap_sid_for_root; use crate::cap::workspace_write_root_contains_path; @@ -23,7 +24,7 @@ use crate::policy::parse_policy; use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::sandbox_utils::ensure_codex_home_exists; use crate::sandbox_utils::inject_git_safe_directory; -use crate::setup::effective_write_roots_for_setup; +use crate::setup::effective_write_roots_for_permissions; use crate::token::LocalSid; use crate::token::create_readonly_token_with_cap; use crate::token::create_workspace_write_token_with_caps_from; @@ -43,10 +44,11 @@ use windows_sys::Win32::Foundation::HANDLE; pub(crate) struct SpawnContext { pub(crate) policy: SandboxPolicy, + pub(crate) permissions: ResolvedWindowsSandboxPermissions, pub(crate) current_dir: PathBuf, pub(crate) sandbox_base: PathBuf, pub(crate) logs_base_dir: Option, - pub(crate) is_workspace_write: bool, + pub(crate) uses_write_capabilities: bool, } pub(crate) struct ElevatedSpawnContext { @@ -110,14 +112,16 @@ fn prepare_spawn_context_common( let logs_base_dir = Some(sandbox_base.clone()); log_start(command, logs_base_dir.as_deref()); - let is_workspace_write = matches!(&policy, SandboxPolicy::WorkspaceWrite { .. }); + let permissions = ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(&policy, cwd); + let uses_write_capabilities = permissions.uses_write_capabilities_for_cwd(cwd, env_map); Ok(SpawnContext { policy, + permissions, current_dir: cwd.to_path_buf(), sandbox_base, logs_base_dir, - is_workspace_write, + uses_write_capabilities, }) } @@ -139,7 +143,7 @@ pub(crate) fn prepare_legacy_spawn_context( inherit_path, add_git_safe_directory, )?; - if should_apply_network_block(&common.policy) { + if common.permissions.should_apply_network_block() { apply_no_network_to_env(env_map)?; } Ok(common) @@ -191,19 +195,20 @@ pub(crate) fn prepare_legacy_session_security( pub(crate) fn legacy_session_capability_roots( policy: &SandboxPolicy, - policy_cwd: &Path, + _policy_cwd: &Path, current_dir: &Path, env_map: &HashMap, codex_home: &Path, ) -> Vec { - let allow_paths = compute_allow_paths(policy, policy_cwd, current_dir, env_map) + let permissions = + ResolvedWindowsSandboxPermissions::from_legacy_policy_for_cwd(policy, current_dir); + let allow_paths = compute_allow_paths_for_permissions(&permissions, current_dir, env_map) .allow .into_iter() .collect::>(); - if matches!(policy, SandboxPolicy::WorkspaceWrite { .. }) { - effective_write_roots_for_setup( - policy, - policy_cwd, + if permissions.uses_write_capabilities_for_cwd(current_dir, env_map) { + effective_write_roots_for_permissions( + &permissions, current_dir, env_map, codex_home, @@ -379,7 +384,7 @@ pub(crate) fn apply_legacy_session_acl_rules( allow_null_device(readonly_sid.as_ptr()); } if persist_aces - && matches!(policy, SandboxPolicy::WorkspaceWrite { .. }) + && !acl_sids.write_root_sids.is_empty() && let Some(workspace_sid) = matching_root_capability(current_dir, acl_sids.write_root_sids) { @@ -417,24 +422,19 @@ pub(crate) fn prepare_elevated_spawn_context( /*add_git_safe_directory*/ true, )?; - let AllowDenyPaths { allow, deny } = compute_allow_paths( - &common.policy, - sandbox_policy_cwd, - &common.current_dir, - env_map, - ); + let AllowDenyPaths { allow, deny } = + compute_allow_paths_for_permissions(&common.permissions, &common.current_dir, env_map); let write_roots: Vec = allow.into_iter().collect(); let deny_write_paths: Vec = deny.into_iter().collect(); - let computed_write_roots_override = if common.is_workspace_write { + let computed_write_roots_override = if common.uses_write_capabilities { Some(write_roots.as_slice()) } else { None }; let write_roots_for_setup = write_roots_override.or(computed_write_roots_override); - let effective_write_roots = if common.is_workspace_write { - effective_write_roots_for_setup( - &common.policy, - sandbox_policy_cwd, + let effective_write_roots = if common.uses_write_capabilities { + effective_write_roots_for_permissions( + &common.permissions, &common.current_dir, env_map, codex_home, @@ -443,7 +443,7 @@ pub(crate) fn prepare_elevated_spawn_context( } else { Vec::new() }; - let setup_write_roots_override = if common.is_workspace_write { + let setup_write_roots_override = if common.uses_write_capabilities { Some(effective_write_roots.as_slice()) } else { write_roots_override @@ -466,24 +466,20 @@ pub(crate) fn prepare_elevated_spawn_context( /*proxy_enforced*/ false, )?; let caps = load_or_create_cap_sids(codex_home)?; - let (psid_to_use, cap_sids) = match &common.policy { - SandboxPolicy::ReadOnly { .. } => ( + let (psid_to_use, cap_sids) = if common.uses_write_capabilities { + let cap_sids = root_capability_sids(codex_home, cwd, effective_write_roots)? + .into_iter() + .map(|root_sid| root_sid.sid_str) + .collect::>(); + if cap_sids.is_empty() { + anyhow::bail!("workspace-write sandbox has no writable root capability SIDs"); + } + (LocalSid::from_string(&cap_sids[0])?, cap_sids) + } else { + ( LocalSid::from_string(&caps.readonly)?, vec![caps.readonly.clone()], - ), - SandboxPolicy::WorkspaceWrite { .. } => { - let cap_sids = root_capability_sids(codex_home, cwd, effective_write_roots)? - .into_iter() - .map(|root_sid| root_sid.sid_str) - .collect::>(); - if cap_sids.is_empty() { - anyhow::bail!("workspace-write sandbox has no writable root capability SIDs"); - } - (LocalSid::from_string(&cap_sids[0])?, cap_sids) - } - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { - unreachable!("dangerous policies rejected before elevated session prep") - } + ) }; unsafe {