permissions: derive compatibility policies from profiles

This commit is contained in:
Michael Bolin
2026-04-24 09:31:17 -07:00
parent 2ec2407a23
commit 32a2927bb9
40 changed files with 498 additions and 413 deletions
+66 -3
View File
@@ -588,6 +588,12 @@ impl FileSystemSandboxPolicy {
.semantic_signature(cwd)
}
/// Returns true when two policies resolve to the same filesystem access
/// model for `cwd`, ignoring incidental entry ordering.
pub fn is_semantically_equivalent_to(&self, other: &Self, cwd: &Path) -> bool {
self.semantic_signature(cwd) == other.semantic_signature(cwd)
}
/// Returns the explicit readable roots resolved against the provided cwd.
pub fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf> {
if self.has_full_disk_read_access() {
@@ -959,9 +965,9 @@ impl FileSystemSandboxPolicy {
has_full_disk_read_access: self.has_full_disk_read_access(),
has_full_disk_write_access: self.has_full_disk_write_access(),
include_platform_defaults: self.include_platform_defaults(),
readable_roots: self.get_readable_roots_with_cwd(cwd),
writable_roots: self.get_writable_roots_with_cwd(cwd),
unreadable_roots: self.get_unreadable_roots_with_cwd(cwd),
readable_roots: sorted_absolute_paths(self.get_readable_roots_with_cwd(cwd)),
writable_roots: sorted_writable_roots(self.get_writable_roots_with_cwd(cwd)),
unreadable_roots: sorted_absolute_paths(self.get_unreadable_roots_with_cwd(cwd)),
unreadable_globs: self.get_unreadable_globs_with_cwd(cwd),
}
}
@@ -1319,6 +1325,20 @@ fn dedup_absolute_paths(
deduped
}
fn sorted_absolute_paths(mut paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
paths.sort_by(|left, right| left.as_path().cmp(right.as_path()));
paths
}
fn sorted_writable_roots(mut roots: Vec<WritableRoot>) -> Vec<WritableRoot> {
for root in &mut roots {
root.read_only_subpaths =
sorted_absolute_paths(std::mem::take(&mut root.read_only_subpaths));
}
roots.sort_by(|left, right| left.root.as_path().cmp(right.root.as_path()));
roots
}
fn normalize_effective_absolute_path(path: AbsolutePathBuf) -> AbsolutePathBuf {
let raw_path = path.to_path_buf();
for ancestor in raw_path.ancestors() {
@@ -2108,6 +2128,49 @@ mod tests {
);
}
#[test]
fn legacy_projection_runtime_enforcement_ignores_entry_order() {
let cwd = TempDir::new().expect("tempdir");
let dot_codex = AbsolutePathBuf::from_absolute_path(cwd.path().join(".codex"))
.expect(".codex path should be absolute");
let current_working_directory = FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
},
access: FileSystemAccessMode::Write,
};
let protected_dot_codex = FileSystemSandboxEntry {
path: FileSystemPath::Path { path: dot_codex },
access: FileSystemAccessMode::Read,
};
let minimal = FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Minimal,
},
access: FileSystemAccessMode::Read,
};
let legacy_order = FileSystemSandboxPolicy::restricted(vec![
minimal.clone(),
current_working_directory.clone(),
protected_dot_codex.clone(),
]);
let reordered = FileSystemSandboxPolicy::restricted(vec![
minimal,
protected_dot_codex,
current_working_directory,
]);
assert!(
legacy_order.is_semantically_equivalent_to(&reordered, cwd.path()),
"entry order should not affect filesystem semantics"
);
assert!(
!reordered
.needs_direct_runtime_enforcement(NetworkSandboxPolicy::Restricted, cwd.path())
);
}
#[test]
fn root_write_with_read_only_child_is_not_full_disk_write() {
let cwd = TempDir::new().expect("tempdir");