diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index cd67e1582b..d2a8d24187 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -122,17 +122,17 @@ pub(crate) struct WindowsProtectedMetadataTarget { } /// Layer: Windows adapter layer. The enforcement layer needs to know whether a -/// protected metadata path already exists or must be denied before the command -/// can create it. +/// protected metadata path already exists, or whether the sandbox should watch +/// the parent for a command-created metadata object. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum WindowsProtectedMetadataMode { /// The protected metadata object exists before launch, so the Windows /// sandbox should deny writes to the object and any canonical target. ExistingDeny, /// The protected metadata object is absent before launch, so the Windows - /// sandbox should create and deny-list a temporary sentinel before command - /// execution can begin. - MissingDenySentinel, + /// sandbox should listen for filesystem creation events and remove any + /// command-created object before reporting the command as denied. + MissingCreationMonitor, } fn windows_sandbox_uses_elevated_backend( @@ -671,8 +671,8 @@ async fn exec_windows_sandbox( WindowsProtectedMetadataMode::ExistingDeny => { codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny } - WindowsProtectedMetadataMode::MissingDenySentinel => { - codex_windows_sandbox::ProtectedMetadataMode::MissingDenySentinel + WindowsProtectedMetadataMode::MissingCreationMonitor => { + codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor } }; codex_windows_sandbox::ProtectedMetadataTarget { @@ -1366,7 +1366,7 @@ fn windows_protected_metadata_mode(path: &AbsolutePathBuf) -> WindowsProtectedMe return WindowsProtectedMetadataMode::ExistingDeny; } - WindowsProtectedMetadataMode::MissingDenySentinel + WindowsProtectedMetadataMode::MissingCreationMonitor } fn has_reopened_writable_descendant( diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 3561d65de1..095a021d48 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -666,15 +666,15 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { protected_metadata_targets: vec![ WindowsProtectedMetadataTarget { path: cwd.join(".agents"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".codex"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".git"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, ], })) @@ -778,15 +778,15 @@ fn windows_elevated_supports_split_write_read_carveouts() { protected_metadata_targets: vec![ WindowsProtectedMetadataTarget { path: expected_root.join(".agents"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: expected_root.join(".codex"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: expected_root.join(".git"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, ], })) @@ -840,11 +840,11 @@ fn windows_metadata_plan_marks_existing_metadata_for_deny() { protected_metadata_targets: vec![ WindowsProtectedMetadataTarget { path: cwd.join(".agents"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".codex"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".git"), @@ -856,7 +856,7 @@ fn windows_metadata_plan_marks_existing_metadata_for_deny() { } #[test] -fn windows_metadata_plan_uses_sentinel_for_nested_missing_git() { +fn windows_metadata_plan_monitors_nested_missing_git() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); let repo = dunce::canonicalize(temp_dir.path()) .expect("canonical temp dir") @@ -904,15 +904,15 @@ fn windows_metadata_plan_uses_sentinel_for_nested_missing_git() { protected_metadata_targets: vec![ WindowsProtectedMetadataTarget { path: cwd.join(".agents"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".codex"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".git"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, ], })) @@ -973,15 +973,15 @@ fn windows_shell_runtime_path_resolves_metadata_overrides() { vec![ WindowsProtectedMetadataTarget { path: cwd.join(".agents"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".codex"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, WindowsProtectedMetadataTarget { path: cwd.join(".git"), - mode: WindowsProtectedMetadataMode::MissingDenySentinel, + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, }, ] ); diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index 964328c356..f95fb9fc99 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -190,8 +190,8 @@ fn protected_metadata_targets_for_windows_session( WindowsProtectedMetadataMode::ExistingDeny => { codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny } - WindowsProtectedMetadataMode::MissingDenySentinel => { - codex_windows_sandbox::ProtectedMetadataMode::MissingDenySentinel + WindowsProtectedMetadataMode::MissingCreationMonitor => { + codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor } }; codex_windows_sandbox::ProtectedMetadataTarget { diff --git a/codex-rs/core/src/unified_exec/process_manager_tests.rs b/codex-rs/core/src/unified_exec/process_manager_tests.rs index 0125f1788e..a41f08b1d8 100644 --- a/codex-rs/core/src/unified_exec/process_manager_tests.rs +++ b/codex-rs/core/src/unified_exec/process_manager_tests.rs @@ -183,15 +183,15 @@ fn open_session_prepares_windows_metadata_overrides_for_unified_exec() { vec![ crate::exec::WindowsProtectedMetadataTarget { path: cwd.join(".agents"), - mode: crate::exec::WindowsProtectedMetadataMode::MissingDenySentinel, + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, }, crate::exec::WindowsProtectedMetadataTarget { path: cwd.join(".codex"), - mode: crate::exec::WindowsProtectedMetadataMode::MissingDenySentinel, + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, }, crate::exec::WindowsProtectedMetadataTarget { path: cwd.join(".git"), - mode: crate::exec::WindowsProtectedMetadataMode::MissingDenySentinel, + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, }, ] ); diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 4dc096cfa9..4b43dfc7eb 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -851,11 +851,17 @@ fn build_payload_deny_write_paths( request.command_cwd, request.env_map, ); - // Sentinel targets are protected by the dedicated metadata payload so setup - // applies a direct deny ACE without inheriting that deny into descendants. - let sentinel_path_keys: HashSet = protected_metadata_targets + // Missing metadata targets are protected by the dedicated metadata payload. + // Do not let generic deny-write setup materialize them as directories. + let missing_metadata_path_keys: HashSet = protected_metadata_targets .iter() - .filter(|target| target.mode == ProtectedMetadataMode::MissingDenySentinel) + .filter(|target| { + matches!( + target.mode, + ProtectedMetadataMode::MissingCreationMonitor + | ProtectedMetadataMode::MissingDenySentinel + ) + }) .map(|target| canonical_path_key(&target.path)) .collect(); let mut deny_write_paths: Vec = explicit_deny_write_paths @@ -870,7 +876,7 @@ fn build_payload_deny_write_paths( }) .collect(); deny_write_paths.extend(allow_deny_paths.deny); - deny_write_paths.retain(|path| !sentinel_path_keys.contains(&canonical_path_key(path))); + deny_write_paths.retain(|path| !missing_metadata_path_keys.contains(&canonical_path_key(path))); deny_write_paths } @@ -1504,13 +1510,14 @@ mod tests { } #[test] - fn payload_deny_write_paths_skip_missing_metadata_sentinels() { + fn payload_deny_write_paths_skip_missing_metadata_targets() { let tmp = TempDir::new().expect("tempdir"); let codex_home = tmp.path().join("codex-home"); let command_cwd = tmp.path().join("workspace"); let command_git = command_cwd.join(".git"); + let command_codex = command_cwd.join(".codex"); let explicit_deny = tmp.path().join("explicit-deny"); - fs::create_dir_all(&command_git).expect("create command .git sentinel"); + fs::create_dir_all(&command_cwd).expect("create workspace"); let policy = SandboxPolicy::WorkspaceWrite { writable_roots: vec![], network_access: false, @@ -1529,10 +1536,16 @@ mod tests { let deny_write_paths = super::build_payload_deny_write_paths( &request, Some(vec![explicit_deny.clone()]), - &[super::ProtectedMetadataTarget { - path: command_git.clone(), - mode: super::ProtectedMetadataMode::MissingDenySentinel, - }], + &[ + super::ProtectedMetadataTarget { + path: command_git, + mode: super::ProtectedMetadataMode::MissingCreationMonitor, + }, + super::ProtectedMetadataTarget { + path: command_codex, + mode: super::ProtectedMetadataMode::MissingDenySentinel, + }, + ], ); assert_eq!(vec![explicit_deny], deny_write_paths);