Make deny canonical for filesystem permission entries (#23493)

## Why
Filesystem permission profiles used `none` for deny-read entries, which
is less direct than the action the entry actually represents. This
change makes `deny` the canonical filesystem permission spelling while
preserving compatibility for older configs that still send `none`.

## What changed
- rename `FileSystemAccessMode::None` to `Deny`
- serialize and generate schemas with `deny` as the canonical value
- retain `none` only as a legacy input alias for temporary config
compatibility
- update filesystem glob diagnostics and regression coverage to use the
canonical spelling
- refresh config and app-server schema fixtures to match the new wire
shape

## Validation
- `cargo test -p codex-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core config_toml_deserializes_permission_profiles
--lib`
- `cargo test -p codex-core
read_write_glob_patterns_still_reject_non_subpath_globs --lib`

Earlier in the session, a broad `cargo test -p codex-core` run reached
unrelated pre-existing failures in timing/snapshot/git-info tests under
this environment; the targeted surfaces touched by this PR passed
cleanly.
This commit is contained in:
viyatb-oai
2026-05-19 11:03:47 -07:00
committed by GitHub
parent 05b8ce4354
commit 3c76081876
44 changed files with 165 additions and 148 deletions
+2 -2
View File
@@ -126,7 +126,7 @@ impl FileSystemPermissions {
match entry.access {
FileSystemAccessMode::Read => read.push(path.clone()),
FileSystemAccessMode::Write => write.push(path.clone()),
FileSystemAccessMode::None => return None,
FileSystemAccessMode::Deny => return None,
}
}
@@ -1788,7 +1788,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}]);
file_system_sandbox_policy.glob_scan_max_depth = Some(2);
+37 -35
View File
@@ -95,7 +95,7 @@ impl NetworkSandboxPolicy {
/// Access mode for a filesystem entry.
///
/// When two equally specific entries target the same path, we compare these by
/// conflict precedence rather than by capability breadth: `none` beats
/// conflict precedence rather than by capability breadth: `deny` beats
/// `write`, and `write` beats `read`.
#[derive(
Debug,
@@ -117,12 +117,14 @@ impl NetworkSandboxPolicy {
pub enum FileSystemAccessMode {
Read,
Write,
None,
/// `none` is a legacy input alias retained temporarily for compatibility.
#[serde(alias = "none")]
Deny,
}
impl FileSystemAccessMode {
pub fn can_read(self) -> bool {
!matches!(self, FileSystemAccessMode::None)
!matches!(self, FileSystemAccessMode::Deny)
}
pub fn can_write(self) -> bool {
@@ -341,7 +343,7 @@ pub enum FileSystemPath {
path: AbsolutePathBuf,
},
/// A git-style glob pattern. Pattern entries currently support
/// FileSystemAccessMode::None only.
/// FileSystemAccessMode::Deny only.
GlobPattern {
pattern: String,
},
@@ -412,7 +414,7 @@ impl FileSystemSandboxPolicy {
&& self
.entries
.iter()
.any(|entry| entry.access == FileSystemAccessMode::None)
.any(|entry| entry.access == FileSystemAccessMode::Deny)
}
pub fn from_legacy_sandbox_policy_preserving_deny_entries(
@@ -429,7 +431,7 @@ impl FileSystemSandboxPolicy {
for deny_entry in existing
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
.filter(|entry| entry.access == FileSystemAccessMode::Deny)
{
if !rebuilt.entries.iter().any(|entry| entry == deny_entry) {
rebuilt.entries.push(deny_entry.clone());
@@ -445,7 +447,7 @@ impl FileSystemSandboxPolicy {
let has_deny_read_entries = existing
.entries
.iter()
.any(|entry| entry.access == FileSystemAccessMode::None);
.any(|entry| entry.access == FileSystemAccessMode::Deny);
if matches!(self.kind, FileSystemSandboxKind::Unrestricted) && has_deny_read_entries {
*self = Self::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
@@ -466,7 +468,7 @@ impl FileSystemSandboxPolicy {
for deny_entry in existing
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
.filter(|entry| entry.access == FileSystemAccessMode::Deny)
{
if !self.entries.iter().any(|entry| entry == deny_entry) {
self.entries.push(deny_entry.clone());
@@ -492,7 +494,7 @@ impl FileSystemSandboxPolicy {
FileSystemPath::Path { .. } => !self.has_same_target_write_override(entry),
FileSystemPath::GlobPattern { .. } => true,
FileSystemPath::Special { value } => match value {
FileSystemSpecialPath::Root => entry.access == FileSystemAccessMode::None,
FileSystemSpecialPath::Root => entry.access == FileSystemAccessMode::Deny,
FileSystemSpecialPath::Minimal | FileSystemSpecialPath::Unknown { .. } => {
false
}
@@ -654,7 +656,7 @@ impl FileSystemSandboxPolicy {
}
let Some(path) = resolve_candidate_path(path, cwd) else {
return FileSystemAccessMode::None;
return FileSystemAccessMode::Deny;
};
self.resolved_entries_with_cwd(cwd)
@@ -662,7 +664,7 @@ impl FileSystemSandboxPolicy {
.filter(|entry| path.as_path().starts_with(entry.path.as_path()))
.max_by_key(resolved_entry_precedence)
.map(|entry| entry.access)
.unwrap_or(FileSystemAccessMode::None)
.unwrap_or(FileSystemAccessMode::Deny)
}
pub fn can_read_path_with_cwd(&self, path: &Path, cwd: &Path) -> bool {
@@ -1062,7 +1064,7 @@ impl FileSystemSandboxPolicy {
dedup_absolute_paths(
self.resolved_entries_with_cwd(cwd)
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
.filter(|entry| entry.access == FileSystemAccessMode::Deny)
.filter(|entry| !self.can_read_path_with_cwd(entry.path.as_path(), cwd))
// Restricted policies already deny reads outside explicit allow roots,
// so materializing the filesystem root here would erase narrower
@@ -1083,7 +1085,7 @@ impl FileSystemSandboxPolicy {
let mut patterns = self
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
.filter(|entry| entry.access == FileSystemAccessMode::Deny)
.filter_map(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => {
Some(AbsolutePathBuf::resolve_path_against_base(pattern, cwd))
@@ -1142,7 +1144,7 @@ impl FileSystemSandboxPolicy {
}
FileSystemPath::Special { value } => match value {
FileSystemSpecialPath::Root => match entry.access {
FileSystemAccessMode::None => {}
FileSystemAccessMode::Deny => {}
FileSystemAccessMode::Read => {}
FileSystemAccessMode::Write => {
unbridgeable_root_write = true;
@@ -2191,7 +2193,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2253,7 +2255,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2354,7 +2356,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_private },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2401,7 +2403,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_private },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2443,7 +2445,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: alias },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2503,7 +2505,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2549,7 +2551,7 @@ mod tests {
path: FileSystemPath::Path {
path: docs_private.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
@@ -2569,7 +2571,7 @@ mod tests {
);
assert_eq!(
policy.resolve_access_with_cwd(docs_private.as_path(), cwd.path()),
FileSystemAccessMode::None
FileSystemAccessMode::Deny
);
assert_eq!(
policy.resolve_access_with_cwd(docs_private_public.as_path(), cwd.path()),
@@ -2710,7 +2712,7 @@ mod tests {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: docs.clone() },
@@ -2746,14 +2748,14 @@ mod tests {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
assert!(!policy.has_full_disk_write_access());
assert_eq!(
policy.resolve_access_with_cwd(root.as_path(), cwd.path()),
FileSystemAccessMode::None
FileSystemAccessMode::Deny
);
}
@@ -2877,7 +2879,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2920,7 +2922,7 @@ mod tests {
.to_string_lossy()
.into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
@@ -2931,7 +2933,7 @@ mod tests {
.to_string_lossy()
.into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
])
);
@@ -2944,7 +2946,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}]);
let actual = policy.materialize_project_roots_with_cwd(cwd.path());
@@ -2957,7 +2959,7 @@ mod tests {
.to_string_lossy()
.into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}])
);
}
@@ -3006,7 +3008,7 @@ mod tests {
#[test]
fn file_system_access_mode_orders_by_conflict_precedence() {
assert!(FileSystemAccessMode::Write > FileSystemAccessMode::Read);
assert!(FileSystemAccessMode::None > FileSystemAccessMode::Write);
assert!(FileSystemAccessMode::Deny > FileSystemAccessMode::Write);
}
#[test]
@@ -3016,7 +3018,7 @@ mod tests {
path: FileSystemPath::Path {
path: denied.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}]);
let rebuilt = FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
@@ -3031,7 +3033,7 @@ mod tests {
== FileSystemPath::Path {
path: denied.clone(),
}
&& entry.access == FileSystemAccessMode::None
&& entry.access == FileSystemAccessMode::Deny
}),
"expected explicit deny entry to be preserved"
);
@@ -3064,14 +3066,14 @@ mod tests {
path: FileSystemPath::Path {
path: AbsolutePathBuf::try_from(path).expect("absolute deny path"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}])
}
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry {
FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern { pattern },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}
}
+4 -4
View File
@@ -4211,7 +4211,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -4268,7 +4268,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: secret },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -5163,7 +5163,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: "/tmp/private/**/*.txt".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
])),
model: "gpt-5".to_string(),
@@ -5191,7 +5191,7 @@ mod tests {
"type": "glob_pattern",
"pattern": "/tmp/private/**/*.txt"
},
"access": "none"
"access": "deny"
}]
})
);