diff --git a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs index e3f200ca71..2a2cb16dbb 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs @@ -58,6 +58,7 @@ use windows_sys::Win32::Security::Authorization::TRUSTEE_W; use windows_sys::Win32::Security::CONTAINER_INHERIT_ACE; use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; use windows_sys::Win32::Security::OBJECT_INHERIT_ACE; +use windows_sys::Win32::Security::WRITE_DAC; use windows_sys::Win32::Storage::FileSystem::DELETE; use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE; @@ -554,6 +555,21 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( format!("convert sandbox users group SID to PSID failed: {err}"), )) })?; + let real_user_sid = resolve_sid(&payload.real_user).map_err(|err| { + anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperSidResolveFailed, + format!( + "resolve SID for real user {} failed: {err}", + payload.real_user + ), + )) + })?; + let real_user_psid = sid_bytes_to_psid(&real_user_sid).map_err(|err| { + anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperSidResolveFailed, + format!("convert real user SID to PSID failed: {err}"), + )) + })?; let caps = load_or_create_cap_sids(&payload.codex_home).map_err(|err| { anyhow::Error::new(SetupFailure::new( @@ -638,8 +654,12 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( let cap_sid_str = caps.workspace; let sandbox_group_sid_str = string_from_sid_bytes(&sandbox_group_sid).map_err(anyhow::Error::msg)?; + let real_user_sid_str = string_from_sid_bytes(&real_user_sid).map_err(anyhow::Error::msg)?; let write_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD; + // Children created by the sandbox user may be owned by that user. Keep the + // interactive user able to refresh those ACLs on the next setup pass. + let real_user_write_mask = write_mask | WRITE_DAC; let mut grant_tasks: Vec = Vec::new(); let mut seen_write_roots: HashSet = HashSet::new(); @@ -668,30 +688,30 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( } else { cap_psid }; - for (label, psid) in [ - ("sandbox_group", sandbox_group_psid), - (cap_label, cap_psid_for_root), + for (label, psid, mask) in [ + ("sandbox_group", sandbox_group_psid, write_mask), + (cap_label, cap_psid_for_root, write_mask), + ("real_user", real_user_psid, real_user_write_mask), ] { - let has = - match path_mask_allows(root, &[psid], write_mask, /*require_all_bits*/ true) { - Ok(h) => h, - Err(e) => { - refresh_errors.push(format!( - "write mask check failed on {} for {label}: {}", + let has = match path_mask_allows(root, &[psid], mask, /*require_all_bits*/ true) { + Ok(h) => h, + Err(e) => { + refresh_errors.push(format!( + "write mask check failed on {} for {label}: {}", + root.display(), + e + )); + log_line( + log, + &format!( + "write mask check failed on {} for {label}: {}; continuing", root.display(), e - )); - log_line( - log, - &format!( - "write mask check failed on {} for {label}: {}; continuing", - root.display(), - e - ), - )?; - false - } - }; + ), + )?; + false + } + }; if !has { need_grant = true; } @@ -700,7 +720,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( log_line( log, &format!( - "granting write ACE to {} for sandbox group and capability SID", + "granting write ACE to {} for sandbox group, capability SID, and real user", root.display() ), )?; @@ -717,6 +737,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( } else { vec![sandbox_group_sid_str.clone(), cap_sid_str.clone()] }; + let real_user_sid_str = real_user_sid_str.clone(); let tx = tx.clone(); scope.spawn(move || { // Convert SID strings to psids locally in this thread. @@ -730,7 +751,25 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( } } - let res = unsafe { ensure_allow_write_aces(&root, &psids) }; + let res = unsafe { ensure_allow_write_aces(&root, &psids) }.and_then(|added| { + let Some(real_user_psid) = + (unsafe { convert_string_sid_to_sid(&real_user_sid_str) }) + else { + return Err(anyhow::anyhow!("convert real user SID failed")); + }; + let real_user_res = unsafe { + ensure_allow_mask_aces_with_inheritance( + &root, + &[real_user_psid], + real_user_write_mask, + OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, + ) + }; + unsafe { + LocalFree(real_user_psid as HLOCAL); + } + real_user_res.map(|real_user_added| added || real_user_added) + }); for psid in psids { unsafe { @@ -889,6 +928,9 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( if !workspace_psid.is_null() { LocalFree(workspace_psid as HLOCAL); } + if !real_user_psid.is_null() { + LocalFree(real_user_psid as HLOCAL); + } } if refresh_only && !refresh_errors.is_empty() { log_line(