mirror of
https://github.com/openai/codex.git
synced 2026-05-04 03:16:31 +00:00
permissions: derive compatibility policies from profiles (#19392)
## Why After #19391, `PermissionProfile` and the split filesystem/network policies could still be stored in parallel. That creates drift risk: a profile can preserve deny globs, external enforcement, or split filesystem entries while a cached projection silently loses those details. This PR makes the profile the runtime source and derives compatibility views from it. ## What Changed - Removes stored filesystem/network sandbox projections from `Permissions` and `SessionConfiguration`; their accessors now derive from the canonical `PermissionProfile`. - Derives legacy `SandboxPolicy` snapshots from profiles only where an older API still needs that field. - Updates MCP connection and elicitation state to track `PermissionProfile` instead of `SandboxPolicy` for auto-approval decisions. - Adds semantic filesystem-policy comparison so cwd changes can preserve richer profiles while still recognizing equivalent legacy projections independent of entry ordering. - Updates config/session tests to assert profile-derived projections instead of parallel stored fields. ## Verification - `cargo test -p codex-core direct_write_roots` - `cargo test -p codex-core runtime_roots_to_legacy_projection` - `cargo test -p codex-app-server requested_permissions_trust_project_uses_permission_profile_intent` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392). * #19395 * #19394 * #19393 * __->__ #19392
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user