From df461fd9eb59bbe28ed32f9f6079dfe71b3f20a5 Mon Sep 17 00:00:00 2001 From: Eva Wong Date: Mon, 4 May 2026 10:02:50 -0700 Subject: [PATCH] Plan Windows metadata targets from filesystem policy --- codex-rs/core/src/exec.rs | 38 +++++++- codex-rs/core/src/exec_tests.rs | 158 ++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index fa536c5dcd..ff00ca69e3 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -110,11 +110,11 @@ pub(crate) struct WindowsSandboxFilesystemOverrides { pub(crate) read_roots_include_platform_defaults: bool, pub(crate) write_roots_override: Option>, pub(crate) additional_deny_write_paths: Vec, + pub(crate) protected_metadata_targets: Vec, } /// Layer: Windows adapter layer. This is the Windows projection of /// `WritableRoot::protected_metadata_names` from `FileSystemSandboxPolicy`. -#[allow(dead_code)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct WindowsProtectedMetadataTarget { pub(crate) path: AbsolutePathBuf, @@ -124,7 +124,6 @@ pub(crate) struct WindowsProtectedMetadataTarget { /// Layer: Windows adapter layer. The enforcement layer needs to know why a /// protected metadata path is absent instead of treating every missing path as /// an existing filesystem object. -#[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum WindowsProtectedMetadataMode { ExistingDeny, @@ -1151,7 +1150,9 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( } } - if additional_deny_write_paths.is_empty() { + let protected_metadata_targets = windows_protected_metadata_targets(&split_writable_roots)?; + + if additional_deny_write_paths.is_empty() && protected_metadata_targets.is_empty() { return Ok(None); } @@ -1163,6 +1164,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( .into_iter() .map(|path| AbsolutePathBuf::from_absolute_path(path).map_err(|err| err.to_string())) .collect::>()?, + protected_metadata_targets, })) } @@ -1283,9 +1285,12 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( Vec::new() }; + let protected_metadata_targets = windows_protected_metadata_targets(&split_writable_roots)?; + if read_roots_override.is_none() && write_roots_override.is_none() && additional_deny_write_paths.is_empty() + && protected_metadata_targets.is_empty() { return Ok(None); } @@ -1296,9 +1301,36 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( read_roots_override, write_roots_override, additional_deny_write_paths, + protected_metadata_targets, })) } +fn windows_protected_metadata_targets( + writable_roots: &[codex_protocol::protocol::WritableRoot], +) -> std::result::Result, String> { + let mut targets = BTreeSet::new(); + for writable_root in writable_roots { + for metadata_name in &writable_root.protected_metadata_names { + let path = + normalize_windows_override_path(writable_root.root.join(metadata_name).as_path())?; + let path = AbsolutePathBuf::from_absolute_path(path).map_err(|err| err.to_string())?; + targets.insert(WindowsProtectedMetadataTarget { + mode: windows_protected_metadata_mode(&path), + path, + }); + } + } + Ok(targets.into_iter().collect()) +} + +fn windows_protected_metadata_mode(path: &AbsolutePathBuf) -> WindowsProtectedMetadataMode { + if std::fs::symlink_metadata(path.as_path()).is_ok() { + return WindowsProtectedMetadataMode::ExistingDeny; + } + + WindowsProtectedMetadataMode::MissingCreationMonitor +} + 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 9d335a81c7..2ea9a8469e 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -663,6 +663,20 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { read_roots_include_platform_defaults: false, write_roots_override: None, additional_deny_write_paths: expected_deny_write_paths, + protected_metadata_targets: vec![ + WindowsProtectedMetadataTarget { + path: cwd.join(".agents"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".codex"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".git"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + ], })) ); } @@ -700,6 +714,7 @@ fn windows_elevated_supports_split_restricted_read_roots() { read_roots_include_platform_defaults: false, write_roots_override: None, additional_deny_write_paths: vec![], + protected_metadata_targets: vec![], })) ); } @@ -707,6 +722,9 @@ fn windows_elevated_supports_split_restricted_read_roots() { #[test] fn windows_elevated_supports_split_write_read_carveouts() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); + let expected_root = dunce::canonicalize(temp_dir.path()) + .expect("canonical temp dir") + .abs(); 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"); @@ -757,6 +775,146 @@ fn windows_elevated_supports_split_write_read_carveouts() { codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(expected_docs) .expect("absolute docs"), ], + protected_metadata_targets: vec![ + WindowsProtectedMetadataTarget { + path: expected_root.join(".agents"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: expected_root.join(".codex"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: expected_root.join(".git"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + ], + })) + ); +} + +#[test] +fn windows_metadata_plan_marks_existing_metadata_for_deny() { + let temp_dir = tempfile::TempDir::new().expect("tempdir"); + let cwd = dunce::canonicalize(temp_dir.path()) + .expect("canonical temp dir") + .abs(); + std::fs::create_dir_all(cwd.join(".git").as_path()).expect("create .git"); + 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 { + value: codex_protocol::permissions::FileSystemSpecialPath::Root, + }, + access: codex_protocol::permissions::FileSystemAccessMode::Read, + }, + codex_protocol::permissions::FileSystemSandboxEntry { + path: codex_protocol::permissions::FileSystemPath::Special { + value: codex_protocol::permissions::FileSystemSpecialPath::project_roots( + /*subpath*/ None, + ), + }, + access: codex_protocol::permissions::FileSystemAccessMode::Write, + }, + ]); + + assert_eq!( + resolve_windows_elevated_filesystem_overrides( + SandboxType::WindowsRestrictedToken, + &policy, + &file_system_policy, + NetworkSandboxPolicy::Restricted, + &cwd, + /*use_windows_elevated_backend*/ true, + ), + Ok(Some(WindowsSandboxFilesystemOverrides { + read_roots_override: None, + read_roots_include_platform_defaults: false, + write_roots_override: None, + additional_deny_write_paths: vec![], + protected_metadata_targets: vec![ + WindowsProtectedMetadataTarget { + path: cwd.join(".agents"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".codex"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".git"), + mode: WindowsProtectedMetadataMode::ExistingDeny, + }, + ], + })) + ); +} + +#[test] +fn windows_metadata_plan_does_not_materialize_nested_missing_git() { + let temp_dir = tempfile::TempDir::new().expect("tempdir"); + let repo = dunce::canonicalize(temp_dir.path()) + .expect("canonical temp dir") + .abs(); + std::fs::create_dir_all(repo.join(".git").as_path()).expect("create parent .git"); + let cwd = repo.join("child"); + std::fs::create_dir_all(cwd.as_path()).expect("create child workspace"); + 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 { + value: codex_protocol::permissions::FileSystemSpecialPath::Root, + }, + access: codex_protocol::permissions::FileSystemAccessMode::Read, + }, + codex_protocol::permissions::FileSystemSandboxEntry { + path: codex_protocol::permissions::FileSystemPath::Special { + value: codex_protocol::permissions::FileSystemSpecialPath::project_roots( + /*subpath*/ None, + ), + }, + access: codex_protocol::permissions::FileSystemAccessMode::Write, + }, + ]); + + assert_eq!( + resolve_windows_elevated_filesystem_overrides( + SandboxType::WindowsRestrictedToken, + &policy, + &file_system_policy, + NetworkSandboxPolicy::Restricted, + &cwd, + /*use_windows_elevated_backend*/ true, + ), + Ok(Some(WindowsSandboxFilesystemOverrides { + read_roots_override: None, + read_roots_include_platform_defaults: false, + write_roots_override: None, + additional_deny_write_paths: vec![], + protected_metadata_targets: vec![ + WindowsProtectedMetadataTarget { + path: cwd.join(".agents"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".codex"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + WindowsProtectedMetadataTarget { + path: cwd.join(".git"), + mode: WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + ], })) ); }