diff --git a/codex-rs/windows-sandbox-rs/src/dpapi.rs b/codex-rs/windows-sandbox-rs/src/dpapi.rs index d254fa1169..c4dcc79fa6 100644 --- a/codex-rs/windows-sandbox-rs/src/dpapi.rs +++ b/codex-rs/windows-sandbox-rs/src/dpapi.rs @@ -1,12 +1,13 @@ use anyhow::anyhow; use anyhow::Result; use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::LocalFree; +use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Security::Cryptography::CryptProtectData; use windows_sys::Win32::Security::Cryptography::CryptUnprotectData; -use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB; +use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_LOCAL_MACHINE; use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_UI_FORBIDDEN; +use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB; fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB { CRYPT_INTEGER_BLOB { @@ -29,12 +30,15 @@ pub fn protect(data: &[u8]) -> Result> { std::ptr::null(), std::ptr::null_mut(), std::ptr::null_mut(), - CRYPTPROTECT_UI_FORBIDDEN, + // Use machine scope so elevated and non-elevated processes can decrypt. + CRYPTPROTECT_UI_FORBIDDEN | CRYPTPROTECT_LOCAL_MACHINE, &mut out_blob, ) }; if ok == 0 { - return Err(anyhow!("CryptProtectData failed: {}", unsafe { GetLastError() })); + return Err(anyhow!("CryptProtectData failed: {}", unsafe { + GetLastError() + })); } let slice = unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec(); @@ -60,15 +64,15 @@ pub fn unprotect(blob: &[u8]) -> Result> { std::ptr::null(), std::ptr::null_mut(), std::ptr::null_mut(), - CRYPTPROTECT_UI_FORBIDDEN, + // Use machine scope so elevated and non-elevated processes can decrypt. + CRYPTPROTECT_UI_FORBIDDEN | CRYPTPROTECT_LOCAL_MACHINE, &mut out_blob, ) }; if ok == 0 { - return Err(anyhow!( - "CryptUnprotectData failed: {}", - unsafe { GetLastError() } - )); + return Err(anyhow!("CryptUnprotectData failed: {}", unsafe { + GetLastError() + })); } let slice = unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec(); diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index c4457845c2..62fda67655 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -63,6 +63,8 @@ pub use setup::run_setup_refresh; #[cfg(target_os = "windows")] pub use setup::sandbox_dir; #[cfg(target_os = "windows")] +pub use setup::sandbox_secrets_dir; +#[cfg(target_os = "windows")] pub use setup::SETUP_VERSION; #[cfg(target_os = "windows")] pub use token::convert_string_sid_to_sid; diff --git a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs index 8aa67a41a5..5847363efb 100644 --- a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs +++ b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs @@ -36,6 +36,7 @@ use windows_sys::Win32::Security::SID_NAME_USE; use codex_windows_sandbox::dpapi_protect; use codex_windows_sandbox::sandbox_dir; +use codex_windows_sandbox::sandbox_secrets_dir; use codex_windows_sandbox::string_from_sid_bytes; use codex_windows_sandbox::to_wide; use codex_windows_sandbox::SETUP_VERSION; @@ -394,6 +395,8 @@ fn write_secrets( ) -> Result<()> { let sandbox_dir = sandbox_dir(codex_home); std::fs::create_dir_all(&sandbox_dir)?; + let secrets_dir = sandbox_secrets_dir(codex_home); + std::fs::create_dir_all(&secrets_dir)?; let offline_blob = dpapi_protect(offline_pwd.as_bytes())?; let online_blob = dpapi_protect(online_pwd.as_bytes())?; let users = SandboxUsersFile { @@ -415,7 +418,7 @@ fn write_secrets( read_roots: Vec::new(), write_roots: Vec::new(), }; - let users_path = sandbox_dir.join("sandbox_users.json"); + let users_path = secrets_dir.join("sandbox_users.json"); let marker_path = sandbox_dir.join("setup_marker.json"); std::fs::write(users_path, serde_json::to_vec_pretty(&users)?)?; std::fs::write(marker_path, serde_json::to_vec_pretty(&marker)?)?; 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 52e60a1da7..39c8a95d4c 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs @@ -14,6 +14,7 @@ use codex_windows_sandbox::load_or_create_cap_sids; use codex_windows_sandbox::log_note; use codex_windows_sandbox::path_mask_allows; use codex_windows_sandbox::sandbox_dir; +use codex_windows_sandbox::sandbox_secrets_dir; use codex_windows_sandbox::string_from_sid_bytes; use codex_windows_sandbox::to_wide; use codex_windows_sandbox::LOG_FILE_NAME; @@ -52,6 +53,8 @@ use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE; +const DENY_ACCESS: i32 = 3; + mod read_acl_mutex; mod sandbox_users; use read_acl_mutex::acquire_read_acl_mutex; @@ -225,6 +228,7 @@ fn lock_sandbox_dir( dir: &Path, real_user: &str, sandbox_group_sid: &[u8], + sandbox_group_access_mode: i32, _log: &mut File, ) -> Result<()> { std::fs::create_dir_all(dir)?; @@ -232,27 +236,31 @@ fn lock_sandbox_dir( let admins_sid = resolve_sid("Administrators")?; let real_sid = resolve_sid(real_user)?; let entries = [ + ( + sandbox_group_sid.to_vec(), + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, + sandbox_group_access_mode, + ), ( system_sid, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, + GRANT_ACCESS, ), ( admins_sid, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, + GRANT_ACCESS, ), ( real_sid, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE, - ), - ( - sandbox_group_sid.to_vec(), - FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, + GRANT_ACCESS, ), ]; unsafe { let mut eas: Vec = Vec::new(); let mut sids: Vec<*mut c_void> = Vec::new(); - for (sid_bytes, mask) in entries.iter().map(|(s, m)| (s, *m)) { + for (sid_bytes, mask, access_mode) in entries.iter().map(|(s, m, a)| (s, *m, *a)) { let sid_str = string_from_sid_bytes(sid_bytes).map_err(anyhow::Error::msg)?; let sid_w = to_wide(OsStr::new(&sid_str)); let mut psid: *mut c_void = std::ptr::null_mut(); @@ -265,7 +273,7 @@ fn lock_sandbox_dir( sids.push(psid); eas.push(EXPLICIT_ACCESS_W { grfAccessPermissions: mask, - grfAccessMode: GRANT_ACCESS, + grfAccessMode: access_mode, grfInheritance: OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, Trustee: TRUSTEE_W { pMultipleTrustee: std::ptr::null_mut(), @@ -605,8 +613,20 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( &sandbox_dir(&payload.codex_home), &payload.real_user, &sandbox_group_sid, + GRANT_ACCESS, log, )?; + lock_sandbox_dir( + &sandbox_secrets_dir(&payload.codex_home), + &payload.real_user, + &sandbox_group_sid, + DENY_ACCESS, + log, + )?; + let legacy_users = sandbox_dir(&payload.codex_home).join("sandbox_users.json"); + if legacy_users.exists() { + let _ = std::fs::remove_file(&legacy_users); + } } unsafe { if !sandbox_group_psid.is_null() { diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 7578e494cd..19e20fc9d0 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -26,7 +26,7 @@ use windows_sys::Win32::Security::CheckTokenMembership; use windows_sys::Win32::Security::FreeSid; use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY; -pub const SETUP_VERSION: u32 = 4; +pub const SETUP_VERSION: u32 = 5; pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020; @@ -36,12 +36,16 @@ pub fn sandbox_dir(codex_home: &Path) -> PathBuf { codex_home.join(".sandbox") } +pub fn sandbox_secrets_dir(codex_home: &Path) -> PathBuf { + codex_home.join(".sandbox-secrets") +} + pub fn setup_marker_path(codex_home: &Path) -> PathBuf { sandbox_dir(codex_home).join("setup_marker.json") } pub fn sandbox_users_path(codex_home: &Path) -> PathBuf { - sandbox_dir(codex_home).join("sandbox_users.json") + sandbox_secrets_dir(codex_home).join("sandbox_users.json") } pub fn run_setup_refresh( @@ -430,10 +434,16 @@ fn filter_sensitive_write_roots(mut roots: Vec, codex_home: &Path) -> V let codex_home_key = crate::audit::normalize_path_key(codex_home); let sbx_dir_key = crate::audit::normalize_path_key(&sandbox_dir(codex_home)); let sbx_dir_prefix = format!("{}/", sbx_dir_key.trim_end_matches('/')); + let secrets_dir_key = crate::audit::normalize_path_key(&sandbox_secrets_dir(codex_home)); + let secrets_dir_prefix = format!("{}/", secrets_dir_key.trim_end_matches('/')); roots.retain(|root| { let key = crate::audit::normalize_path_key(root); - key != codex_home_key && key != sbx_dir_key && !key.starts_with(&sbx_dir_prefix) + key != codex_home_key + && key != sbx_dir_key + && !key.starts_with(&sbx_dir_prefix) + && key != secrets_dir_key + && !key.starts_with(&secrets_dir_prefix) }); roots }