permissions: derive compatibility policies from profiles

This commit is contained in:
Michael Bolin
2026-04-25 20:21:54 -07:00
parent bd3d0ee6d6
commit dfcde491be
40 changed files with 569 additions and 439 deletions

View File

@@ -482,10 +482,7 @@ impl PermissionProfile {
FileSystemSandboxKind::ExternalSandbox => Self::External {
network: network_sandbox_policy,
},
FileSystemSandboxKind::Unrestricted
if enforcement == SandboxEnforcement::Disabled
&& network_sandbox_policy.is_enabled() =>
{
FileSystemSandboxKind::Unrestricted if enforcement == SandboxEnforcement::Disabled => {
Self::Disabled
}
FileSystemSandboxKind::Restricted | FileSystemSandboxKind::Unrestricted => {
@@ -1867,6 +1864,17 @@ mod tests {
Ok(())
}
#[test]
fn disabled_permission_profile_ignores_runtime_network_policy() {
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::Disabled,
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
);
assert_eq!(permission_profile, PermissionProfile::Disabled);
}
#[test]
fn permission_profile_from_runtime_permissions_preserves_external_sandbox() {
let permission_profile = PermissionProfile::from_runtime_permissions(

View File

@@ -631,6 +631,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() {
@@ -949,9 +955,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),
}
}
@@ -1257,6 +1263,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() {
@@ -2145,6 +2165,31 @@ mod tests {
);
}
#[test]
fn legacy_projection_runtime_enforcement_ignores_entry_order() {
let cwd = TempDir::new().expect("tempdir");
let legacy_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let legacy_order =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&legacy_policy, cwd.path());
let mut reordered_entries = legacy_order.entries.clone();
reordered_entries.reverse();
let reordered = FileSystemSandboxPolicy::restricted(reordered_entries);
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");