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
Unverified
parent 05b8ce4354
commit 3c76081876
44 changed files with 165 additions and 148 deletions
@@ -277,7 +277,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -1085,7 +1085,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
+1 -1
View File
@@ -631,7 +631,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -8524,7 +8524,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -4913,7 +4913,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -69,7 +69,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FileSystemAccessMode = "read" | "write" | "none";
export type FileSystemAccessMode = "read" | "write" | "deny";
@@ -178,7 +178,7 @@ v2_enum_from_core!(
pub enum FileSystemAccessMode from CoreFileSystemAccessMode {
Read,
Write,
None
Deny
}
);
@@ -421,7 +421,7 @@ fn additional_file_system_permissions_preserves_canonical_entries() {
path: CoreFileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: CoreFileSystemAccessMode::None,
access: CoreFileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: NonZeroUsize::new(2),
@@ -445,7 +445,7 @@ fn additional_file_system_permissions_preserves_canonical_entries() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]),
}
@@ -445,7 +445,7 @@ mod thread_processor_behavior_tests {
path: FileSystemPath::GlobPattern {
pattern: "/tmp/project/**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]),
NetworkSandboxPolicy::Restricted,
+17 -7
View File
@@ -836,13 +836,23 @@
"type": "object"
},
"FileSystemAccessMode": {
"description": "Access mode for a filesystem entry.\n\nWhen two equally specific entries target the same path, we compare these by conflict precedence rather than by capability breadth: `none` beats `write`, and `write` beats `read`.",
"enum": [
"read",
"write",
"none"
],
"type": "string"
"description": "Access mode for a filesystem entry.\n\nWhen two equally specific entries target the same path, we compare these by conflict precedence rather than by capability breadth: `deny` beats `write`, and `write` beats `read`.",
"oneOf": [
{
"enum": [
"read",
"write"
],
"type": "string"
},
{
"description": "`none` is a legacy input alias retained temporarily for compatibility.",
"enum": [
"deny"
],
"type": "string"
}
]
},
"FilesystemPermissionToml": {
"anyOf": [
+7 -2
View File
@@ -740,6 +740,7 @@ default_permissions = "workspace"
[permissions.workspace.filesystem]
":minimal" = "read"
"/tmp/secret.env" = "deny"
[permissions.workspace.filesystem.":workspace_roots"]
"." = "write"
@@ -777,6 +778,10 @@ allow_upstream_proxy = false
":minimal".to_string(),
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
),
(
"/tmp/secret.env".to_string(),
FilesystemPermissionToml::Access(FileSystemAccessMode::Deny),
),
(
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
@@ -1700,7 +1705,7 @@ async fn workspace_root_glob_none_compiles_to_filesystem_pattern_entry() -> std:
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
(".".to_string(), FileSystemAccessMode::Write),
("**/*.env".to_string(), FileSystemAccessMode::None),
("**/*.env".to_string(), FileSystemAccessMode::Deny),
])),
)]),
}),
@@ -1739,7 +1744,7 @@ async fn workspace_root_glob_none_compiles_to_filesystem_pattern_entry() -> std:
path: FileSystemPath::GlobPattern {
pattern: expected_pattern,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
})
);
}
+2 -2
View File
@@ -2031,7 +2031,7 @@ fn apply_managed_filesystem_constraints(
path: codex_protocol::permissions::FileSystemPath::GlobPattern {
pattern: deny_read.as_str().to_string(),
},
access: codex_protocol::permissions::FileSystemAccessMode::None,
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
}
} else {
let Ok(path) = AbsolutePathBuf::try_from(deny_read.as_str()) else {
@@ -2039,7 +2039,7 @@ fn apply_managed_filesystem_constraints(
};
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Path { path },
access: codex_protocol::permissions::FileSystemAccessMode::None,
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
}
};
if !file_system_sandbox_policy
+10 -10
View File
@@ -243,7 +243,7 @@ pub(crate) fn compile_permission_profile(
push_warning(
startup_warnings,
format!(
"Filesystem glob `{pattern}` uses `read` or `write` access, which is not fully supported by this platform's sandboxing. Use an exact path or trailing `/**` subtree rule instead. `none` deny-read globs are supported."
"Filesystem glob `{pattern}` uses `read` or `write` access, which is not fully supported by this platform's sandboxing. Use an exact path or trailing `/**` subtree rule instead. `deny` globs are supported."
),
);
}
@@ -414,7 +414,7 @@ fn compile_filesystem_permission(
Some(FileSystemSpecialPath::ProjectRoots { .. }) | None => true,
Some(_) => false,
};
if has_glob && *access == FileSystemAccessMode::None && can_compile_as_pattern {
if has_glob && *access == FileSystemAccessMode::Deny && can_compile_as_pattern {
// Scoped glob syntax is a first-class filesystem policy
// pattern entry. Literal scoped paths continue through the
// exact-path parser so existing path semantics stay intact.
@@ -449,7 +449,7 @@ fn compile_filesystem_access_path(
return compile_filesystem_path(path, startup_warnings);
}
if access == FileSystemAccessMode::None {
if access == FileSystemAccessMode::Deny {
// At this point `path` is an unscoped filesystem table key. Top-level
// glob deny entries still go through the absolute-path parser before
// becoming policy patterns; relative project-root glob syntax is
@@ -520,10 +520,10 @@ fn compile_scoped_filesystem_pattern(
// Pattern entries currently mean deny-read only. Supporting broader access
// modes here would imply glob-based read/write allow semantics that the
// sandbox policy does not express yet.
if access != FileSystemAccessMode::None {
if access != FileSystemAccessMode::Deny {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("filesystem glob subpath `{subpath}` only supports `none` access"),
format!("filesystem glob subpath `{subpath}` only supports `deny` access"),
));
}
let subpath = parse_relative_subpath(subpath)?;
@@ -559,7 +559,7 @@ fn compile_read_write_glob_path(path: &str, access: FileSystemAccessMode) -> io:
Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"filesystem glob path `{path}` only supports `none` access; use an exact path or trailing `/**` for `{access}` subtree access"
"filesystem glob path `{path}` only supports `deny` access; use an exact path or trailing `/**` for `{access}` subtree access"
),
))
}
@@ -569,7 +569,7 @@ fn unsupported_read_write_glob_paths(filesystem: &FilesystemPermissionsToml) ->
for (path, permission) in &filesystem.entries {
match permission {
FilesystemPermissionToml::Access(access) => {
if *access != FileSystemAccessMode::None
if *access != FileSystemAccessMode::Deny
&& contains_glob_chars(remove_trailing_glob_suffix(path))
{
patterns.push(path.clone());
@@ -577,7 +577,7 @@ fn unsupported_read_write_glob_paths(filesystem: &FilesystemPermissionsToml) ->
}
FilesystemPermissionToml::Scoped(scoped_entries) => {
for (subpath, access) in scoped_entries {
if *access != FileSystemAccessMode::None
if *access != FileSystemAccessMode::Deny
&& contains_glob_chars(remove_trailing_glob_suffix(subpath))
{
patterns.push(format!("{path}/{subpath}"));
@@ -597,7 +597,7 @@ fn unbounded_unreadable_globstar_paths(filesystem: &FilesystemPermissionsToml) -
let mut patterns = Vec::new();
for (path, permission) in &filesystem.entries {
match permission {
FilesystemPermissionToml::Access(FileSystemAccessMode::None) => {
FilesystemPermissionToml::Access(FileSystemAccessMode::Deny) => {
if path.contains("**") {
patterns.push(path.clone());
}
@@ -605,7 +605,7 @@ fn unbounded_unreadable_globstar_paths(filesystem: &FilesystemPermissionsToml) -
FilesystemPermissionToml::Access(_) => {}
FilesystemPermissionToml::Scoped(scoped_entries) => {
for (subpath, access) in scoped_entries {
if *access == FileSystemAccessMode::None && subpath.contains("**") {
if *access == FileSystemAccessMode::Deny && subpath.contains("**") {
patterns.push(format!("{path}/{subpath}"));
}
}
@@ -326,7 +326,7 @@ fn read_write_glob_warnings_skip_supported_deny_read_globs_and_trailing_subpaths
(
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
("**/*.env".to_string(), FileSystemAccessMode::None),
("**/*.env".to_string(), FileSystemAccessMode::Deny),
("docs/**".to_string(), FileSystemAccessMode::Read),
("src/**/*.rs".to_string(), FileSystemAccessMode::Write),
])),
@@ -340,7 +340,7 @@ fn read_write_glob_warnings_skip_supported_deny_read_globs_and_trailing_subpaths
"/tmp/**/*.log".to_string(),
":workspace_roots/src/**/*.rs".to_string()
],
"`none` glob patterns are supported as deny-read rules; only `read`/`write` globs should warn"
"`deny` glob patterns are supported as deny-read rules; only `read`/`write` globs should warn"
);
}
@@ -351,8 +351,8 @@ fn unreadable_globstar_warning_is_suppressed_when_scan_depth_is_configured() {
entries: BTreeMap::from([(
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
("**/*.env".to_string(), FileSystemAccessMode::None),
("*.pem".to_string(), FileSystemAccessMode::None),
("**/*.env".to_string(), FileSystemAccessMode::Deny),
("*.pem".to_string(), FileSystemAccessMode::Deny),
])),
)]),
};
@@ -435,7 +435,7 @@ fn read_write_glob_patterns_still_reject_non_subpath_globs() {
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert!(
err.to_string()
.contains("filesystem glob path `src/**/*.rs` only supports `none` access"),
.contains("filesystem glob path `src/**/*.rs` only supports `deny` access"),
"{err}"
);
}
+3 -3
View File
@@ -698,7 +698,7 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Path { path: blocked },
access: codex_protocol::permissions::FileSystemAccessMode::None,
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
@@ -846,7 +846,7 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&blocked)
.expect("absolute blocked"),
},
access: codex_protocol::permissions::FileSystemAccessMode::None,
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
@@ -908,7 +908,7 @@ fn windows_elevated_supports_unreadable_globs() {
path: codex_protocol::permissions::FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: codex_protocol::permissions::FileSystemAccessMode::None,
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
+1 -1
View File
@@ -218,7 +218,7 @@ fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() {
path: FileSystemPath::Path {
path: blocked_absolute,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
+2 -2
View File
@@ -3522,7 +3522,7 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
};
let mut existing_file_system_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
@@ -7137,7 +7137,7 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy
path: FileSystemPath::GlobPattern {
pattern: format!("{cwd_display}/**/*.env"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
policy
}
+1 -1
View File
@@ -441,7 +441,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -2106,7 +2106,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
path: FileSystemPath::GlobPattern {
pattern: "**/.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
@@ -281,7 +281,7 @@ fn shell_request_escalation_execution_is_explicit() {
path: FileSystemPath::Path {
path: AbsolutePathBuf::from_absolute_path("/tmp/secret").unwrap(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let network_sandbox_policy = NetworkSandboxPolicy::Restricted;
+1 -1
View File
@@ -143,7 +143,7 @@ fn deny_read_blocks_explicit_escalation_but_preserves_policy_bypass() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}]);
assert_eq!(
+1 -1
View File
@@ -148,7 +148,7 @@ fn workspace_write_with_unreadable_path(unreadable_path: AbsolutePathBuf) -> Per
path: FileSystemPath::Path {
path: unreadable_path,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
+1 -1
View File
@@ -380,7 +380,7 @@ async fn shell_command_enforces_glob_deny_read_policy() -> Result<()> {
path: FileSystemPath::GlobPattern {
pattern: format!("{}/**/*.env", config.cwd.as_path().display()),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
config
.permissions
+1 -1
View File
@@ -2774,7 +2774,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
path: FileSystemPath::GlobPattern {
pattern: format!("{}/**/*.env", config.cwd.as_path().display()),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
config
.permissions
+1 -1
View File
@@ -527,7 +527,7 @@ async fn view_image_tool_applies_local_sandbox_read_denies() -> anyhow::Result<(
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
let permission_profile = PermissionProfile::from_runtime_permissions(
&file_system_sandbox_policy,
+4 -4
View File
@@ -91,13 +91,13 @@ async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> a
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: future_secret,
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let permission_profile = PermissionProfile::from_runtime_permissions(
@@ -173,11 +173,11 @@ async fn windows_elevated_enforces_exact_and_glob_deny_read_policy() -> anyhow::
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: exact_secret },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let permission_profile = PermissionProfile::from_runtime_permissions(
+10 -10
View File
@@ -1347,7 +1347,7 @@ mod tests {
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry {
FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern { pattern },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}
}
@@ -1547,7 +1547,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -1672,7 +1672,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: link_private },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -1989,7 +1989,7 @@ mod tests {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(Some(".secrets".into())),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2172,7 +2172,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2299,7 +2299,7 @@ mod tests {
path: FileSystemPath::Path {
path: blocked.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
@@ -2368,7 +2368,7 @@ mod tests {
path: FileSystemPath::Path {
path: blocked.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
@@ -2447,7 +2447,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: allowed },
@@ -2498,7 +2498,7 @@ mod tests {
path: FileSystemPath::Path {
path: blocked.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -2542,7 +2542,7 @@ mod tests {
path: FileSystemPath::Path {
path: blocked_file.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -805,7 +805,7 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
path: FileSystemPath::Path {
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let permission_profile = PermissionProfile::from_runtime_permissions(
@@ -873,7 +873,7 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
path: FileSystemPath::Path {
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
@@ -931,7 +931,7 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
path: FileSystemPath::Path {
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let permission_profile = PermissionProfile::from_runtime_permissions(
+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"
}]
})
);
+2 -2
View File
@@ -183,7 +183,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let permissions = PermissionProfile::from_runtime_permissions(
@@ -228,7 +228,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: denied_path },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: allowed_path },
+4 -4
View File
@@ -28,7 +28,7 @@ pub fn normalize_additional_permissions(
let glob_scan_max_depth = file_system.glob_scan_max_depth;
for entry in file_system.entries {
if matches!(&entry.path, FileSystemPath::GlobPattern { .. })
&& entry.access != FileSystemAccessMode::None
&& entry.access != FileSystemAccessMode::Deny
{
return Err(
"glob file system permissions only support deny-read entries".to_string(),
@@ -221,7 +221,7 @@ fn effective_glob_scan_depth(
entries
.iter()
.any(|entry| {
entry.access == FileSystemAccessMode::None
entry.access == FileSystemAccessMode::Deny
&& matches!(&entry.path, FileSystemPath::GlobPattern { .. })
})
.then_some(match depth {
@@ -273,7 +273,7 @@ fn retain_constraining_deny_entries(
let mut retained_entries = Vec::new();
for entry in source_entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
.filter(|entry| entry.access == FileSystemAccessMode::Deny)
{
if !deny_entry_constrains_accepted_grant(entry, accepted_entries, cwd) {
continue;
@@ -340,7 +340,7 @@ fn access_covers(requested: FileSystemAccessMode, granted: FileSystemAccessMode)
match granted {
FileSystemAccessMode::Read => requested.can_read(),
FileSystemAccessMode::Write => requested.can_write(),
FileSystemAccessMode::None => false,
FileSystemAccessMode::Deny => false,
}
}
@@ -58,7 +58,7 @@ fn root_write_policy_with_carveouts_still_uses_platform_sandbox() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: blocked },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -187,7 +187,7 @@ fn normalize_additional_permissions_preserves_deny_globs() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}],
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
}),
@@ -203,7 +203,7 @@ fn normalize_additional_permissions_preserves_deny_globs() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}],
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
}),
@@ -427,7 +427,7 @@ fn intersect_permission_profiles_materializes_cwd_deny_entries() {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -448,7 +448,7 @@ fn intersect_permission_profiles_materializes_cwd_deny_entries() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: request_cwd },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -480,7 +480,7 @@ fn intersect_permission_profiles_drops_deny_entries_without_filesystem_grants()
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: secret },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -520,7 +520,7 @@ fn intersect_permission_profiles_rejects_concrete_grants_matched_by_requested_de
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
@@ -558,7 +558,7 @@ fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
};
let permissions = PermissionProfile {
file_system: Some(FileSystemPermissions {
@@ -586,7 +586,7 @@ fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
path: FileSystemPath::GlobPattern {
pattern: request_cwd.join("**/*.env").to_string_lossy().into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
@@ -657,7 +657,7 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
};
let requested = PermissionProfile {
file_system: Some(FileSystemPermissions {
@@ -689,7 +689,7 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
.to_string_lossy()
.into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: std::num::NonZeroUsize::new(4),
@@ -712,7 +712,7 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
};
let requested = PermissionProfile {
file_system: Some(FileSystemPermissions {
@@ -744,7 +744,7 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
.to_string_lossy()
.into_owned(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -775,7 +775,7 @@ fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roo
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]),
&FileSystemPermissions::from_read_write_roots(
@@ -787,7 +787,7 @@ fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roo
assert_eq!(
merged_policy.entries.contains(&FileSystemSandboxEntry {
path: FileSystemPath::Path { path: denied_path },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}),
true
);
@@ -806,7 +806,7 @@ fn merge_file_system_policy_with_additional_permissions_carries_bounded_glob_sca
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
};
let merged_policy = merge_file_system_policy_with_additional_permissions(
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
@@ -853,7 +853,7 @@ fn effective_file_system_sandbox_policy_returns_base_policy_without_additional_p
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: denied_path },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -883,7 +883,7 @@ fn effective_file_system_sandbox_policy_merges_additional_write_roots() {
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
let additional_permissions = PermissionProfile {
@@ -900,7 +900,7 @@ fn effective_file_system_sandbox_policy_merges_additional_write_roots() {
assert_eq!(
effective_policy.entries.contains(&FileSystemSandboxEntry {
path: FileSystemPath::Path { path: denied_path },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}),
true
);
+3 -3
View File
@@ -195,7 +195,7 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: unreadable },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -267,7 +267,7 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Path { path: unreadable },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]);
@@ -372,7 +372,7 @@ fn unreadable_glob_policy_includes_canonicalized_static_prefix() {
let mut policy = FileSystemSandboxPolicy::default();
policy.entries.push(FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern { pattern },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
});
let seatbelt_policy = build_seatbelt_unreadable_glob_policy(&policy, temp_dir.path());
+1 -1
View File
@@ -294,7 +294,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: "**/.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -960,7 +960,7 @@ pub(crate) fn format_additional_permissions_rule(
.entries
.iter()
.flatten()
.filter(|entry| entry.access == FileSystemAccessMode::None),
.filter(|entry| entry.access == FileSystemAccessMode::Deny),
);
if !denied_reads.is_empty() {
parts.push(format!("deny read {denied_reads}"));
@@ -1820,7 +1820,7 @@ mod tests {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
]),
glob_scan_max_depth: None,
@@ -112,7 +112,7 @@ async fn submission_includes_configured_active_permission_profile() {
path: FileSystemPath::GlobPattern {
pattern: "/home/user/project/secrets/**".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -245,7 +245,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
path: FileSystemPath::GlobPattern {
pattern: "**/.secret".to_string(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
},
],
glob_scan_max_depth: None,
@@ -43,7 +43,7 @@ pub fn resolve_windows_deny_read_paths(
path: FileSystemPath::GlobPattern {
pattern: pattern.clone(),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
})
.collect(),
);
@@ -205,7 +205,7 @@ mod tests {
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry {
FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern { pattern },
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}
}
@@ -214,7 +214,7 @@ mod tests {
path: FileSystemPath::Path {
path: AbsolutePathBuf::from_absolute_path(path).expect("absolute path"),
},
access: FileSystemAccessMode::None,
access: FileSystemAccessMode::Deny,
}
}