mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user