Use Windows metadata creation monitor

This commit is contained in:
Eva Wong
2026-05-05 13:21:26 -07:00
parent 0cebb6116c
commit c595c596f8
5 changed files with 52 additions and 39 deletions

View File

@@ -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(

View File

@@ -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,
},
]
);

View File

@@ -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 {

View File

@@ -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,
},
]
);

View File

@@ -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<String> = 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<String> = 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<PathBuf> = 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);