mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Skip missing paths in filesystem sandbox entries (#34598)
## Why Default read-only protections for project metadata should apply when paths such as `.git`, `.agents`, and `.codex` exist, without causing sandbox setup to materialize missing paths as ACL targets. ## What changed - Add an optional `missing_path_behavior` to filesystem sandbox entries and mark default project-metadata protections with `skip`. - Preserve the behavior through permission transforms and exec/MCP protocol serialization while keeping existing path wire variants stable. - Ignore skip-missing entries when projecting configuration or Windows sandbox overrides, while retaining explicit metadata carveouts. ## Testing - Cover protocol round trips for path and special-path entries. - Verify default metadata protections and Windows explicit carveout handling. GitOrigin-RevId: 6df13dadacdd131c44aab9f15a967c81051355c1
This commit is contained in:
@@ -368,6 +368,7 @@ impl TryFrom<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
|
||||
Ok(Self {
|
||||
path: value.path.try_into()?,
|
||||
access: value.access.to_core(),
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,12 +591,14 @@ fn additional_file_system_permissions_preserves_canonical_entries() {
|
||||
value: CoreFileSystemSpecialPath::Root,
|
||||
},
|
||||
access: CoreFileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
CoreFileSystemSandboxEntry {
|
||||
path: CoreFileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: CoreFileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: NonZeroUsize::new(2),
|
||||
|
||||
@@ -3071,6 +3071,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -3118,6 +3119,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
|
||||
@@ -520,12 +520,14 @@ mod thread_processor_behavior_tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "/tmp/project/**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -148,6 +148,7 @@ mod tests {
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_utils_path_uri::LegacyAppPathString;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -192,6 +193,62 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_state_serializes_skip_missing_entries_as_missing_path_behavior() {
|
||||
let sandbox_cwd = PathUri::from_host_native_path(
|
||||
std::env::current_dir().expect("current directory should be available"),
|
||||
)
|
||||
.expect("current directory should convert to a URI");
|
||||
let sandbox_state = SandboxState {
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
codex_linux_sandbox_exe: None,
|
||||
sandbox_cwd,
|
||||
use_legacy_landlock: false,
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_value(&sandbox_state).expect("serialize sandbox state");
|
||||
let serialized_text = serde_json::to_string(&serialized).expect("serialize JSON text");
|
||||
assert!(
|
||||
!serialized_text.contains("generated_default_path"),
|
||||
"MCP sandbox metadata must preserve FileSystemPath's stable wire variants"
|
||||
);
|
||||
assert!(
|
||||
!serialized_text.contains("generated_default_special"),
|
||||
"MCP sandbox metadata must preserve FileSystemPath's stable wire variants"
|
||||
);
|
||||
|
||||
let entries = serialized
|
||||
.pointer("/permissionProfile/file_system/entries")
|
||||
.and_then(Value::as_array)
|
||||
.expect("workspace-write profile should contain filesystem entries");
|
||||
let skip_missing_entries = entries
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
entry.get("missing_path_behavior").and_then(Value::as_str) == Some("skip")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(
|
||||
!skip_missing_entries.is_empty(),
|
||||
"skip-missing entries should be represented as optional missing_path_behavior"
|
||||
);
|
||||
assert!(
|
||||
skip_missing_entries.iter().all(|entry| {
|
||||
matches!(
|
||||
entry.pointer("/path/type").and_then(Value::as_str),
|
||||
Some("path" | "special")
|
||||
)
|
||||
}),
|
||||
"skip-missing entries should use the stable path/special variants"
|
||||
);
|
||||
|
||||
let deserialized: SandboxState =
|
||||
serde_json::from_value(serialized).expect("deserialize sandbox state");
|
||||
assert_eq!(
|
||||
deserialized.permission_profile,
|
||||
sandbox_state.permission_profile
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_stdio_requires_local_stdio_availability() {
|
||||
let runtime_context = McpRuntimeContext::new(
|
||||
|
||||
@@ -2037,18 +2037,21 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: cwd_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: cwd_root.join("docs"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
);
|
||||
@@ -2295,12 +2298,14 @@ async fn permission_profile_override_keeps_memories_root_out_of_legacy_projectio
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
@@ -2461,6 +2466,7 @@ async fn workspace_root_glob_none_compiles_to_filesystem_pattern_entry() -> std:
|
||||
pattern: expected_pattern,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -2800,6 +2806,7 @@ async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io::
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
)),
|
||||
"expected profile extending :workspace to keep inherited :slash_tmp writes, policy: {policy:?}"
|
||||
@@ -2812,6 +2819,7 @@ async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io::
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
)),
|
||||
"expected child :tmpdir read entry to replace the inherited write entry, policy: {policy:?}"
|
||||
@@ -2824,6 +2832,7 @@ async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io::
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
)),
|
||||
"expected inherited :tmpdir write entry to be removed, policy: {policy:?}"
|
||||
@@ -3446,6 +3455,7 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
|
||||
),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -3492,6 +3502,7 @@ async fn permissions_profiles_allow_unknown_special_paths_with_nested_entries()
|
||||
value: FileSystemSpecialPath::unknown(":future_special_path", Some("docs".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
);
|
||||
assert!(
|
||||
@@ -4247,6 +4258,7 @@ exclude_slash_tmp = true
|
||||
.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.abs() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
@@ -4257,6 +4269,7 @@ exclude_slash_tmp = true
|
||||
path: extra_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
);
|
||||
for subpath in [".git", ".agents", ".codex"] {
|
||||
@@ -4271,6 +4284,9 @@ exclude_slash_tmp = true
|
||||
),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: Some(
|
||||
codex_protocol::permissions::FileSystemSandboxEntryMissingPathBehavior::Skip,
|
||||
),
|
||||
}),
|
||||
"case `{name}` should materialize `{subpath}` for the runtime workspace \
|
||||
root"
|
||||
@@ -9993,12 +10009,14 @@ async fn permission_profile_override_preserves_split_write_roots() -> std::io::R
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: outside_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
|
||||
@@ -2410,6 +2410,7 @@ fn apply_managed_filesystem_constraints(
|
||||
pattern: deny_read.as_str().to_string(),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
} else {
|
||||
let Ok(path) = AbsolutePathBuf::try_from(deny_read.as_str()) else {
|
||||
@@ -2418,6 +2419,7 @@ fn apply_managed_filesystem_constraints(
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
};
|
||||
if !file_system_sandbox_policy
|
||||
|
||||
@@ -236,6 +236,10 @@ fn insert_filesystem_permission_toml(
|
||||
entries: &mut BTreeMap<String, FilesystemPermissionToml>,
|
||||
entry: FileSystemSandboxEntry,
|
||||
) {
|
||||
if entry.skips_missing_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
match entry.path {
|
||||
FileSystemPath::Path { path } => {
|
||||
entries.insert(
|
||||
@@ -526,6 +530,7 @@ fn compile_filesystem_permission(
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: compile_filesystem_access_path(path, *access, startup_warnings)?,
|
||||
access: *access,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
}
|
||||
FilesystemPermissionToml::Scoped(scoped_entries) => {
|
||||
@@ -544,6 +549,7 @@ fn compile_filesystem_permission(
|
||||
pattern: compile_scoped_filesystem_pattern(path, subpath, *access)?,
|
||||
},
|
||||
access: *access,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
entries.push(entry);
|
||||
} else {
|
||||
@@ -551,6 +557,7 @@ fn compile_filesystem_permission(
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: compile_scoped_filesystem_path(path, subpath, startup_warnings)?,
|
||||
access: *access,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,6 +577,7 @@ fn read_write_trailing_glob_suffix_compiles_as_subpath() -> std::io::Result<()>
|
||||
value: FileSystemSpecialPath::project_roots(Some("docs".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
"trailing /** should compile as a subtree path instead of a glob pattern"
|
||||
);
|
||||
|
||||
@@ -152,18 +152,21 @@ fn workspace_write_permission_profile_with_private_denials() -> PermissionProfil
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some("private".to_string())),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: project_roots_glob_pattern(Path::new("private/**")),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -1182,12 +1182,14 @@ fn managed_cwd_write_profile_has_filesystem_restrictions() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -1208,6 +1210,7 @@ fn managed_unresolvable_write_profile_has_filesystem_restrictions() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -1217,6 +1220,7 @@ fn managed_unresolvable_write_profile_has_filesystem_restrictions() {
|
||||
),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -1237,6 +1241,7 @@ fn managed_full_disk_write_profile_has_no_filesystem_restrictions() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
|
||||
@@ -142,12 +142,14 @@ fn writable_windows_policy_without_sandbox_backend_still_requires_approval() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -441,6 +441,7 @@ fn windows_restricted_token_rejects_managed_root_write_profiles() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -512,6 +513,7 @@ fn windows_elevated_allows_split_restricted_read_policies() {
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -543,6 +545,7 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -550,6 +553,7 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
|
||||
.expect("absolute docs"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -582,6 +586,7 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -589,6 +594,7 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
|
||||
.expect("absolute docs"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -624,6 +630,7 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -632,10 +639,12 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs.clone() },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -679,6 +688,7 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -687,10 +697,12 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: blocked },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -725,6 +737,7 @@ fn windows_elevated_supports_split_restricted_read_roots() {
|
||||
.expect("absolute docs"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -761,6 +774,7 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -769,6 +783,7 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -776,6 +791,7 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
.expect("absolute docs"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -803,6 +819,52 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_workspace_defaults_do_not_hide_explicit_metadata_carveouts() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let cwd = temp_dir.path().canonicalize().expect("canonical cwd").abs();
|
||||
|
||||
let default_profile = PermissionProfile::workspace_write();
|
||||
let default_overrides = resolve_windows_elevated_filesystem_overrides(
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
&default_profile,
|
||||
&cwd,
|
||||
/*use_windows_elevated_backend*/ true,
|
||||
)
|
||||
.expect("resolve workspace defaults");
|
||||
assert!(
|
||||
default_overrides.is_none_or(|overrides| overrides.additional_deny_write_paths.is_empty())
|
||||
);
|
||||
|
||||
for name in codex_protocol::permissions::PROTECTED_METADATA_PATH_NAMES {
|
||||
let (mut explicit_policy, network_policy) = default_profile.to_runtime_permissions();
|
||||
explicit_policy
|
||||
.entries
|
||||
.push(codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(Some(
|
||||
(*name).into(),
|
||||
)),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
let explicit_profile =
|
||||
PermissionProfile::from_runtime_permissions(&explicit_policy, network_policy);
|
||||
|
||||
let overrides = resolve_windows_elevated_filesystem_overrides(
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
&explicit_profile,
|
||||
&cwd,
|
||||
/*use_windows_elevated_backend*/ true,
|
||||
)
|
||||
.expect("resolve explicit metadata carveout")
|
||||
.expect("explicit metadata carveout needs an override");
|
||||
assert_eq!(overrides.additional_deny_write_paths, vec![cwd.join(name)]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_elevated_supports_unreadable_split_carveouts() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
@@ -815,6 +877,7 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -823,6 +886,7 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -830,6 +894,7 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
|
||||
.expect("absolute blocked"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -874,6 +939,7 @@ fn windows_elevated_supports_unreadable_globs() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -882,12 +948,14 @@ fn windows_elevated_supports_unreadable_globs() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -927,6 +995,7 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -935,6 +1004,7 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -942,6 +1012,7 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
|
||||
.expect("absolute docs"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
@@ -949,6 +1020,7 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
|
||||
.expect("absolute nested"),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -473,18 +473,21 @@ async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Re
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: denied_glob.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -230,12 +230,14 @@ fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: blocked_absolute,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -278,12 +280,14 @@ fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: docs_absolute,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -327,6 +331,7 @@ fn missing_project_dot_codex_config_requires_approval() {
|
||||
path: cwd.join(".codex"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
|
||||
assert!(!is_write_patch_constrained_to_writable_paths(
|
||||
|
||||
@@ -98,6 +98,7 @@ fn root_write_managed_profile_with_enabled_network_is_untagged() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
@@ -148,6 +149,7 @@ fn profile_policy_tag_reports_closest_legacy_mode() {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
},
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -4606,10 +4606,12 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs_dir },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
@@ -4653,6 +4655,7 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let mut existing_file_system_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
@@ -4716,6 +4719,7 @@ async fn session_configuration_apply_permission_profile_accepts_direct_write_roo
|
||||
path: external_write_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
@@ -4965,12 +4969,14 @@ async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_up
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: original_cwd.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
session_configuration
|
||||
@@ -6208,6 +6214,7 @@ async fn request_permissions_tool_resolves_relative_paths_against_selected_envir
|
||||
path: environment_cwd.join("relative.txt"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -6294,6 +6301,7 @@ async fn request_permissions_response_materializes_session_cwd_grants_before_rec
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -8858,6 +8866,7 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy
|
||||
pattern: format!("{cwd_display}/**/*.env"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
policy
|
||||
}
|
||||
|
||||
@@ -444,12 +444,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -2283,6 +2283,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
pattern: "**/.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
|
||||
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
|
||||
@@ -69,6 +69,7 @@ fn read_only_file_system_sandbox_policy() -> FileSystemSandboxPolicy {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}])
|
||||
}
|
||||
|
||||
@@ -79,12 +80,14 @@ fn denied_read_file_system_sandbox_policy() -> FileSystemSandboxPolicy {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
}
|
||||
@@ -299,12 +302,14 @@ fn shell_request_escalation_execution_is_explicit() {
|
||||
path: AbsolutePathBuf::from_absolute_path("/tmp/original/output").unwrap(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::from_absolute_path("/tmp/secret").unwrap(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
|
||||
@@ -151,6 +151,7 @@ fn deny_read_blocks_explicit_escalation_and_policy_bypass() {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -140,12 +140,14 @@ fn workspace_write_with_read_only_root(read_only_root: AbsolutePathBuf) -> Permi
|
||||
path: read_only_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
@@ -162,12 +164,14 @@ fn workspace_write_with_unreadable_path(unreadable_path: AbsolutePathBuf) -> Per
|
||||
path: unreadable_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -3588,6 +3588,7 @@ allow_local_binding = true
|
||||
pattern: format!("{}/**/*.env", test.config.cwd.as_path().display()),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
assert!(
|
||||
file_system_sandbox_policy.has_denied_read_restrictions(),
|
||||
|
||||
@@ -115,6 +115,7 @@ async fn extension_tool_receives_turn_environment_sandbox() -> Result<()> {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
|
||||
@@ -1129,6 +1129,7 @@ fn read_only_sandbox(readable_root: PathBuf) -> FileSystemSandboxContext {
|
||||
path: readable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
))
|
||||
@@ -1142,6 +1143,7 @@ fn workspace_write_sandbox(writable_root: PathBuf) -> FileSystemSandboxContext {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
))
|
||||
@@ -1361,12 +1363,14 @@ async fn remote_exec_materializes_target_roots_before_sandbox_selection() -> Res
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -453,6 +453,7 @@ async fn shell_command_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
pattern: format!("{}/**/*.env", config.cwd.as_path().display()),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
config
|
||||
.permissions
|
||||
|
||||
@@ -3095,6 +3095,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
pattern: format!("{}/**/*.env", config.cwd.as_path().display()),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
config
|
||||
.permissions
|
||||
|
||||
@@ -337,6 +337,7 @@ fn permission_profile_from_toml(profile: &str) -> Result<PermissionProfile> {
|
||||
Ok(FileSystemSandboxEntry {
|
||||
path,
|
||||
access: *access,
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
@@ -532,6 +532,7 @@ async fn view_image_tool_applies_local_sandbox_read_denies() -> anyhow::Result<(
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
|
||||
@@ -134,24 +134,28 @@ async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> a
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: future_secret,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -197,6 +201,59 @@ async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> a
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_home)]
|
||||
async fn windows_elevated_does_not_create_missing_workspace_metadata() -> anyhow::Result<()> {
|
||||
let codex_home =
|
||||
codex_home_for_windows_sandbox_test("windows-elevated-missing-metadata-codex-home")?;
|
||||
let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.path().as_os_str());
|
||||
stage_windows_sandbox_helpers()?;
|
||||
let workspace = TempDir::new()?;
|
||||
let cwd = dunce::canonicalize(workspace.path())?.abs();
|
||||
let permission_profile = PermissionProfile::workspace_write()
|
||||
.materialize_project_roots_with_workspace_roots(std::slice::from_ref(&cwd));
|
||||
|
||||
let output = process_exec_tool_call(
|
||||
ExecParams {
|
||||
command: vec![
|
||||
"cmd.exe".to_string(),
|
||||
"/D".to_string(),
|
||||
"/C".to_string(),
|
||||
"echo sandbox-ok".to_string(),
|
||||
],
|
||||
cwd: cwd.clone(),
|
||||
expiration: 10_000.into(),
|
||||
capture_policy: ExecCapturePolicy::ShellTool,
|
||||
env: HashMap::new(),
|
||||
network: None,
|
||||
network_environment_id: None,
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Elevated,
|
||||
windows_sandbox_private_desktop: false,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
},
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
std::slice::from_ref(&cwd),
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(output.exit_code, 0, "sandboxed command should complete");
|
||||
for name in codex_protocol::permissions::PROTECTED_METADATA_PATH_NAMES {
|
||||
let path = cwd.join(name);
|
||||
assert!(
|
||||
!path.exists(),
|
||||
"elevated setup should not create missing workspace metadata: {}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_home)]
|
||||
async fn windows_elevated_enforces_deny_read_and_protects_setup_marker() -> anyhow::Result<()> {
|
||||
@@ -219,22 +276,26 @@ async fn windows_elevated_enforces_deny_read_and_protects_setup_marker() -> anyh
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: exact_secret },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -743,11 +743,13 @@ mod tests {
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -871,7 +873,75 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filesystem_protocol_round_trips_permission_paths_as_uris() {
|
||||
fn filesystem_protocol_round_trips_permission_entries() {
|
||||
let native_cwd = std::env::current_dir().expect("current directory");
|
||||
let cwd = PathUri::from_host_native_path(&native_cwd).expect("cwd URI");
|
||||
let file_system = ManagedFileSystemPermissions::Restricted {
|
||||
entries: vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: native_cwd.clone().try_into().expect("absolute cwd"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Path {
|
||||
path: native_cwd.join(".git").try_into().expect("absolute path"),
|
||||
},
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots {
|
||||
subpath: Some(".codex".into()),
|
||||
},
|
||||
},
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
],
|
||||
glob_scan_max_depth: Some(2.try_into().expect("non-zero depth")),
|
||||
};
|
||||
let permissions = PermissionProfile::Managed {
|
||||
file_system,
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
let sandbox =
|
||||
FileSystemSandboxContext::from_permission_profile_with_cwd(permissions, cwd.clone());
|
||||
|
||||
let serialized = serde_json::to_value(&sandbox).expect("serialize sandbox");
|
||||
|
||||
assert_eq!(
|
||||
serialized["permissions"]["file_system"]["entries"][0]["path"]["path"],
|
||||
serde_json::json!(cwd.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
serialized["permissions"]["file_system"]["entries"][1]["path"]["type"],
|
||||
serde_json::json!("path")
|
||||
);
|
||||
assert_eq!(
|
||||
serialized["permissions"]["file_system"]["entries"][1]["missing_path_behavior"],
|
||||
serde_json::json!("skip")
|
||||
);
|
||||
assert_eq!(
|
||||
serialized["permissions"]["file_system"]["entries"][2]["path"]["type"],
|
||||
serde_json::json!("special")
|
||||
);
|
||||
assert_eq!(
|
||||
serialized["permissions"]["file_system"]["entries"][2]["missing_path_behavior"],
|
||||
serde_json::json!("skip")
|
||||
);
|
||||
assert!(!serialized.to_string().contains("generated_default_path"));
|
||||
assert!(!serialized.to_string().contains("generated_default_special"));
|
||||
assert_eq!(
|
||||
serde_json::from_value::<FileSystemSandboxContext>(serialized)
|
||||
.expect("deserialize sandbox"),
|
||||
sandbox
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filesystem_protocol_round_trips_legacy_policy_paths_as_uris() {
|
||||
let native_cwd = std::env::current_dir().expect("current directory");
|
||||
let cwd = PathUri::from_host_native_path(&native_cwd).expect("cwd URI");
|
||||
let mut file_system_policy =
|
||||
@@ -880,6 +950,7 @@ mod tests {
|
||||
path: native_cwd.try_into().expect("absolute cwd"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
file_system_policy.glob_scan_max_depth = Some(2);
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -206,12 +206,12 @@ fn add_helper_runtime_permissions(
|
||||
cwd: &std::path::Path,
|
||||
) {
|
||||
if !file_system_policy.has_full_disk_read_access() {
|
||||
let minimal_read_entry = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
let minimal_read_entry = FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
};
|
||||
FileSystemAccessMode::Read,
|
||||
);
|
||||
if !file_system_policy.entries.contains(&minimal_read_entry) {
|
||||
file_system_policy.entries.push(minimal_read_entry);
|
||||
}
|
||||
@@ -222,12 +222,12 @@ fn add_helper_runtime_permissions(
|
||||
continue;
|
||||
}
|
||||
|
||||
file_system_policy.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
file_system_policy.entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path {
|
||||
path: helper_read_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
});
|
||||
FileSystemAccessMode::Read,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,6 +618,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let sandbox_context = codex_file_system::FileSystemSandboxContext::from_permission_profile(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
@@ -710,6 +711,7 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,6 +722,7 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special { value },
|
||||
access,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,7 @@ mod tests {
|
||||
path: absolute_test_path("remote-root"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let permissions =
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted);
|
||||
@@ -427,6 +428,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let permissions =
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted);
|
||||
|
||||
@@ -52,6 +52,7 @@ async fn remote_file_system_sends_path_and_sandbox_cwd_uris_without_native_conve
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
|
||||
@@ -205,12 +205,14 @@ async fn remote_process_keeps_sandbox_helper_visible_with_restricted_reads() ->
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
@@ -272,12 +274,14 @@ async fn remote_tty_process_uses_configured_sandbox_helper_with_hostile_path() -
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
@@ -332,6 +336,7 @@ async fn remote_process_preserves_empty_workspace_roots() -> Result<()> {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
|
||||
@@ -396,6 +396,7 @@ fn read_only_sandbox(path: std::path::PathBuf) -> FileSystemSandboxContext {
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
))
|
||||
|
||||
@@ -86,6 +86,7 @@ pub(crate) fn read_only_sandbox(readable_root: std::path::PathBuf) -> FileSystem
|
||||
path: readable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}])
|
||||
}
|
||||
|
||||
@@ -98,6 +99,7 @@ pub(crate) fn workspace_write_sandbox(
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}])
|
||||
}
|
||||
|
||||
|
||||
@@ -235,6 +235,7 @@ async fn remote_read_file_materializes_environment_workspace_roots() -> Result<(
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
@@ -276,6 +277,7 @@ async fn remote_read_file_preserves_empty_workspace_roots() -> Result<()> {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
|
||||
@@ -143,12 +143,14 @@ fn summarizes_managed_workspace_write_permission_profile() {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: cache_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntryMissingPathBehavior;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
@@ -160,6 +161,8 @@ impl TryFrom<ExecFileSystemPath> for FileSystemPath {
|
||||
pub struct ExecFileSystemSandboxEntry {
|
||||
pub path: ExecFileSystemPath,
|
||||
pub access: FileSystemAccessMode,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub missing_path_behavior: Option<FileSystemSandboxEntryMissingPathBehavior>,
|
||||
}
|
||||
|
||||
impl From<FileSystemSandboxEntry> for ExecFileSystemSandboxEntry {
|
||||
@@ -167,6 +170,7 @@ impl From<FileSystemSandboxEntry> for ExecFileSystemSandboxEntry {
|
||||
Self {
|
||||
path: value.path.into(),
|
||||
access: value.access,
|
||||
missing_path_behavior: value.missing_path_behavior,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +182,7 @@ impl TryFrom<ExecFileSystemSandboxEntry> for FileSystemSandboxEntry {
|
||||
Ok(Self {
|
||||
path: value.path.try_into()?,
|
||||
access: value.access,
|
||||
missing_path_behavior: value.missing_path_behavior,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,14 +394,14 @@ fn create_filesystem_args(
|
||||
.iter()
|
||||
.filter(|entry| entry.access == FileSystemAccessMode::Read)
|
||||
.filter_map(|entry| {
|
||||
let FileSystemPath::Special {
|
||||
value:
|
||||
FileSystemSpecialPath::ProjectRoots {
|
||||
subpath: Some(subpath),
|
||||
},
|
||||
} = &entry.path
|
||||
else {
|
||||
return None;
|
||||
let subpath = match &entry.path {
|
||||
FileSystemPath::Special {
|
||||
value:
|
||||
FileSystemSpecialPath::ProjectRoots {
|
||||
subpath: Some(subpath),
|
||||
},
|
||||
} => subpath,
|
||||
_ => return None,
|
||||
};
|
||||
// Automatic repo-metadata read masks are skipped here so the
|
||||
// metadata handling below can apply the root-scoped
|
||||
@@ -1348,6 +1348,7 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern { pattern },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,6 +1427,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
unreadable_glob_entry(format!("{}/**/*.env", temp_dir.path().display())),
|
||||
]);
|
||||
@@ -1470,12 +1472,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1544,10 +1548,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1594,6 +1600,7 @@ mod tests {
|
||||
path: logical_memories_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args =
|
||||
@@ -1633,6 +1640,7 @@ mod tests {
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let err =
|
||||
@@ -1669,10 +1677,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_private },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1704,10 +1714,12 @@ mod tests {
|
||||
path: workspace_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked_root },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1750,6 +1762,7 @@ mod tests {
|
||||
path: workspace_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args =
|
||||
@@ -1799,6 +1812,7 @@ mod tests {
|
||||
path: workspace_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args = create_filesystem_args(&policy, &workspace, NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||
@@ -1845,6 +1859,7 @@ mod tests {
|
||||
path: link_workspace_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args =
|
||||
@@ -1914,30 +1929,35 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".git".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".agents".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".codex".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1972,24 +1992,28 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".vscode".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".secrets".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2105,6 +2129,7 @@ mod tests {
|
||||
.expect("absolute readable root"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args =
|
||||
@@ -2132,6 +2157,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let args =
|
||||
@@ -2169,10 +2195,12 @@ mod tests {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2242,16 +2270,19 @@ mod tests {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: docs_public.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2294,18 +2325,21 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: blocked.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: allowed.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2363,18 +2397,21 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: blocked.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: allowed_file.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2444,14 +2481,17 @@ mod tests {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: allowed },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2493,12 +2533,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: blocked.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2537,12 +2579,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: blocked_file.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -455,6 +455,7 @@ fn build_preflight_bwrap_argv(
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
let preflight_command = vec![resolve_true_command()];
|
||||
build_bwrap_argv(
|
||||
|
||||
@@ -221,10 +221,12 @@ fn split_only_filesystem_policy_requires_direct_runtime_enforcement() {
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -245,10 +247,12 @@ fn root_write_read_only_carveout_requires_direct_runtime_enforcement() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -566,10 +570,12 @@ fn resolve_permission_profile_preserves_direct_runtime_profile() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -620,10 +626,12 @@ fn legacy_landlock_rejects_split_only_filesystem_policies() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -787,6 +787,7 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
@@ -794,18 +795,21 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
||||
.expect("absolute helper dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -855,6 +859,7 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
@@ -862,24 +867,28 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
||||
.expect("absolute helper dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(allowed.as_path()).expect("absolute allowed dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
@@ -926,12 +935,14 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(blocked.as_path()).expect("absolute blocked dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -194,6 +194,7 @@ fn builds_permissions_from_profile() {
|
||||
path: writable_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
@@ -230,18 +231,21 @@ fn builds_permissions_from_profile_with_denied_reads() {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: denied_glob.to_string_lossy().into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -89,15 +89,19 @@ impl FileSystemPermissions {
|
||||
) -> Self {
|
||||
let mut entries = Vec::new();
|
||||
if let Some(read) = read {
|
||||
entries.extend(read.into_iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Read,
|
||||
entries.extend(read.into_iter().map(|path| {
|
||||
FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path { path },
|
||||
FileSystemAccessMode::Read,
|
||||
)
|
||||
}));
|
||||
}
|
||||
if let Some(write) = write {
|
||||
entries.extend(write.into_iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Write,
|
||||
entries.extend(write.into_iter().map(|path| {
|
||||
FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path { path },
|
||||
FileSystemAccessMode::Write,
|
||||
)
|
||||
}));
|
||||
}
|
||||
Self {
|
||||
@@ -636,12 +640,12 @@ impl From<&FileSystemSandboxPolicy> for FileSystemPermissions {
|
||||
let entries = match value.kind {
|
||||
FileSystemSandboxKind::Restricted => value.entries.clone(),
|
||||
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
|
||||
vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]
|
||||
FileSystemAccessMode::Write,
|
||||
)]
|
||||
}
|
||||
};
|
||||
Self {
|
||||
@@ -2497,6 +2501,7 @@ mod tests {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
file_system_sandbox_policy.glob_scan_max_depth = Some(2);
|
||||
|
||||
@@ -2542,6 +2547,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: NonZeroUsize::new(2),
|
||||
},
|
||||
@@ -2685,6 +2691,7 @@ mod tests {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: NonZeroUsize::new(2),
|
||||
};
|
||||
|
||||
@@ -174,6 +174,37 @@ impl FileSystemSpecialPath {
|
||||
pub struct FileSystemSandboxEntry {
|
||||
pub path: FileSystemPath,
|
||||
pub access: FileSystemAccessMode,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub missing_path_behavior: Option<FileSystemSandboxEntryMissingPathBehavior>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FileSystemSandboxEntryMissingPathBehavior {
|
||||
Skip,
|
||||
}
|
||||
|
||||
impl FileSystemSandboxEntry {
|
||||
pub fn new(path: FileSystemPath, access: FileSystemAccessMode) -> Self {
|
||||
Self {
|
||||
path,
|
||||
access,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_missing_path(path: FileSystemPath, access: FileSystemAccessMode) -> Self {
|
||||
Self {
|
||||
path,
|
||||
access,
|
||||
missing_path_behavior: Some(FileSystemSandboxEntryMissingPathBehavior::Skip),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skips_missing_path(&self) -> bool {
|
||||
self.missing_path_behavior == Some(FileSystemSandboxEntryMissingPathBehavior::Skip)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -355,12 +386,12 @@ pub fn project_roots_glob_pattern(subpath: &Path) -> String {
|
||||
}
|
||||
|
||||
fn read_only_file_system_entries() -> Vec<FileSystemSandboxEntry> {
|
||||
vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]
|
||||
FileSystemAccessMode::Read,
|
||||
)]
|
||||
}
|
||||
|
||||
impl Default for FileSystemSandboxPolicy {
|
||||
@@ -398,6 +429,26 @@ impl FileSystemSandboxPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes entries that should be skipped when their paths are missing.
|
||||
///
|
||||
/// Callers that materialize filesystem ACL targets should not turn these
|
||||
/// entries into newly-created sentinel paths.
|
||||
pub fn remove_skip_missing_path_entries(&mut self) {
|
||||
self.entries.retain(|entry| !entry.skips_missing_path());
|
||||
}
|
||||
|
||||
pub fn has_explicit_non_write_entry_for_path_with_cwd(&self, path: &Path, cwd: &Path) -> bool {
|
||||
let Some(path) = resolve_candidate_path(path, cwd) else {
|
||||
return false;
|
||||
};
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
||||
self.entries.iter().any(|entry| {
|
||||
!entry.skips_missing_path()
|
||||
&& !entry.access.can_write()
|
||||
&& resolve_entry_path(&entry.path, cwd.as_ref()).as_ref() == Some(&path)
|
||||
})
|
||||
}
|
||||
|
||||
fn has_root_access(&self, predicate: impl Fn(FileSystemAccessMode) -> bool) -> bool {
|
||||
matches!(self.kind, FileSystemSandboxKind::Restricted)
|
||||
&& self.entries.iter().any(|entry| {
|
||||
@@ -449,12 +500,12 @@ impl FileSystemSandboxPolicy {
|
||||
.iter()
|
||||
.any(|entry| entry.access == FileSystemAccessMode::Deny);
|
||||
if matches!(self.kind, FileSystemSandboxKind::Unrestricted) && has_deny_read_entries {
|
||||
*self = Self::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
*self = Self::restricted(vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
FileSystemAccessMode::Write,
|
||||
)]);
|
||||
}
|
||||
|
||||
if !matches!(self.kind, FileSystemSandboxKind::Restricted) {
|
||||
@@ -521,44 +572,38 @@ impl FileSystemSandboxPolicy {
|
||||
exclude_tmpdir_env_var: bool,
|
||||
exclude_slash_tmp: bool,
|
||||
) -> Self {
|
||||
let mut entries = vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
let mut entries = vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}];
|
||||
FileSystemAccessMode::Read,
|
||||
)];
|
||||
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
if !exclude_slash_tmp {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
if !exclude_tmpdir_env_var {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
entries.extend(
|
||||
writable_roots
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Write,
|
||||
}),
|
||||
);
|
||||
entries.extend(writable_roots.iter().cloned().map(|path| {
|
||||
FileSystemSandboxEntry::new(FileSystemPath::Path { path }, FileSystemAccessMode::Write)
|
||||
}));
|
||||
|
||||
append_default_read_only_project_root_subpath_if_no_explicit_rule(&mut entries, ".git");
|
||||
append_default_read_only_project_root_subpath_if_no_explicit_rule(&mut entries, ".agents");
|
||||
@@ -758,6 +803,7 @@ impl FileSystemSandboxPolicy {
|
||||
},
|
||||
},
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
}));
|
||||
}
|
||||
FileSystemPath::GlobPattern { pattern } => {
|
||||
@@ -767,11 +813,13 @@ impl FileSystemSandboxPolicy {
|
||||
pattern: resolve_project_roots_glob_pattern(subpath, root),
|
||||
},
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
}));
|
||||
} else {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern { pattern },
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -779,12 +827,14 @@ impl FileSystemSandboxPolicy {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
});
|
||||
}
|
||||
FileSystemPath::Special { value } => {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special { value },
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -824,10 +874,10 @@ impl FileSystemSandboxPolicy {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
});
|
||||
self.entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path { path: path.clone() },
|
||||
FileSystemAccessMode::Read,
|
||||
));
|
||||
}
|
||||
|
||||
self
|
||||
@@ -843,10 +893,10 @@ impl FileSystemSandboxPolicy {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
self.entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path { path: path.clone() },
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
|
||||
self
|
||||
@@ -871,10 +921,10 @@ impl FileSystemSandboxPolicy {
|
||||
entry.access.can_write()
|
||||
&& matches!(&entry.path, FileSystemPath::Path { path: existing } if existing == path)
|
||||
}) {
|
||||
self.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
self.entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Path { path: path.clone() },
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
|
||||
for protected_path in default_read_only_subpaths_for_writable_root(
|
||||
@@ -1258,12 +1308,12 @@ impl From<&SandboxPolicy> for FileSystemSandboxPolicy {
|
||||
SandboxPolicy::DangerFullAccess => FileSystemSandboxPolicy::unrestricted(),
|
||||
SandboxPolicy::ExternalSandbox { .. } => FileSystemSandboxPolicy::external_sandbox(),
|
||||
SandboxPolicy::ReadOnly { .. } => {
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}])
|
||||
FileSystemAccessMode::Read,
|
||||
)])
|
||||
}
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
@@ -1600,45 +1650,39 @@ fn legacy_runtime_file_system_policy_for_cwd(
|
||||
};
|
||||
|
||||
let mut entries = vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemAccessMode::Write,
|
||||
),
|
||||
];
|
||||
|
||||
if !*exclude_slash_tmp {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
if !*exclude_tmpdir_env_var {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
entries.push(FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
FileSystemAccessMode::Write,
|
||||
));
|
||||
}
|
||||
entries.extend(
|
||||
writable_roots
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Write,
|
||||
}),
|
||||
);
|
||||
entries.extend(writable_roots.iter().cloned().map(|path| {
|
||||
FileSystemSandboxEntry::new(FileSystemPath::Path { path }, FileSystemAccessMode::Write)
|
||||
}));
|
||||
|
||||
if let Ok(cwd_root) = AbsolutePathBuf::from_absolute_path(cwd) {
|
||||
for protected_path in default_read_only_subpaths_for_writable_root(
|
||||
@@ -1689,10 +1733,10 @@ fn append_default_read_only_entry_if_no_explicit_rule(
|
||||
return;
|
||||
}
|
||||
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
entries.push(FileSystemSandboxEntry::skip_missing_path(
|
||||
path,
|
||||
access: FileSystemAccessMode::Read,
|
||||
});
|
||||
FileSystemAccessMode::Read,
|
||||
));
|
||||
}
|
||||
|
||||
fn has_explicit_resolved_path_entry(
|
||||
@@ -1887,6 +1931,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -1896,6 +1941,7 @@ mod tests {
|
||||
),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1928,6 +1974,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let writable_roots = policy.get_writable_roots_with_cwd(cwd.path());
|
||||
@@ -1957,31 +2004,33 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".git".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".agents".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".codex".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -2023,12 +2072,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: explicit_dot_codex.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2068,6 +2119,7 @@ mod tests {
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
assert!(!file_system_policy.can_write_path_with_cwd(&dot_git_config, cwd.path()));
|
||||
@@ -2114,21 +2166,23 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
];
|
||||
expected_entries.extend(PROTECTED_METADATA_PATH_NAMES.iter().map(|name| {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some((*name).into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}
|
||||
FileSystemAccessMode::Read,
|
||||
)
|
||||
}));
|
||||
expected_entries.extend(
|
||||
default_read_only_subpaths_for_writable_root(
|
||||
@@ -2136,9 +2190,11 @@ mod tests {
|
||||
/*protect_missing_dot_codex*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Read,
|
||||
.map(|path| {
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Path { path },
|
||||
FileSystemAccessMode::Read,
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2190,10 +2246,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2246,16 +2304,19 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2313,6 +2374,7 @@ mod tests {
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let writable_roots = policy.get_writable_roots_with_cwd(cwd.path());
|
||||
@@ -2353,10 +2415,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_private },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2400,10 +2464,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_private },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2442,10 +2508,12 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: alias },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2502,10 +2570,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: link_blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2542,22 +2612,26 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: docs_private.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: docs_private_public.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2589,10 +2663,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2675,10 +2751,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2713,10 +2791,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2743,12 +2823,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2769,14 +2851,17 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2796,6 +2881,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let actual = policy
|
||||
@@ -2814,6 +2900,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let actual = policy
|
||||
@@ -2834,6 +2921,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let actual = policy.with_additional_writable_roots(&cwd, std::slice::from_ref(&extra));
|
||||
@@ -2846,10 +2934,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: extra },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
);
|
||||
@@ -2868,18 +2958,21 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".git".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2894,24 +2987,28 @@ mod tests {
|
||||
path: first.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: second.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: first.join(".git"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: second.join(".git"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
@@ -2923,6 +3020,7 @@ mod tests {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
@@ -2934,6 +3032,7 @@ mod tests {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
);
|
||||
@@ -2947,6 +3046,7 @@ mod tests {
|
||||
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let actual = policy.materialize_project_roots_with_cwd(cwd.path());
|
||||
@@ -2960,6 +3060,7 @@ mod tests {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}])
|
||||
);
|
||||
}
|
||||
@@ -2975,6 +3076,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let actual =
|
||||
@@ -2988,19 +3090,21 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: extra.clone()
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
FileSystemSandboxEntry::skip_missing_path(
|
||||
FileSystemPath::Path {
|
||||
path: extra.join(".git")
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemAccessMode::Read,
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -3019,6 +3123,7 @@ mod tests {
|
||||
path: denied.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let rebuilt = FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
@@ -3054,6 +3159,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
deny_entry,
|
||||
]);
|
||||
@@ -3067,6 +3173,7 @@ mod tests {
|
||||
path: AbsolutePathBuf::try_from(path).expect("absolute deny path"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}])
|
||||
}
|
||||
|
||||
@@ -3074,6 +3181,7 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern { pattern },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4930,6 +4930,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
assert!(read_only.has_full_disk_read_access());
|
||||
assert!(!read_only.has_full_disk_write_access());
|
||||
@@ -4940,6 +4941,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
assert!(writable.has_full_disk_read_access());
|
||||
assert!(writable.has_full_disk_write_access());
|
||||
@@ -4970,10 +4972,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -5021,16 +5025,19 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: secret },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -5089,14 +5096,17 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: docs_public },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -5133,6 +5143,7 @@ mod tests {
|
||||
path: external_write_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
let err = policy
|
||||
@@ -6115,6 +6126,7 @@ mod tests {
|
||||
pattern: "/tmp/private/**/*.txt".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])),
|
||||
model: "gpt-5".to_string(),
|
||||
|
||||
@@ -64,6 +64,7 @@ fn restricted_file_system_uses_platform_sandbox_without_managed_network() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
SandboxablePreference::Auto,
|
||||
@@ -194,12 +195,14 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
@@ -243,14 +246,17 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: denied_path },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: allowed_path },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
);
|
||||
@@ -274,6 +280,7 @@ fn managed_mitm_ca_bundle_becomes_readable_for_restricted_sandbox() {
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
@@ -291,12 +298,14 @@ fn managed_mitm_ca_bundle_becomes_readable_for_restricted_sandbox() {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: managed_bundle_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
);
|
||||
@@ -342,6 +351,7 @@ fn wsl1_rejects_linux_bubblewrap_path() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
assert!(matches!(
|
||||
@@ -391,6 +401,7 @@ fn wsl1_allows_non_bubblewrap_linux_paths() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
assert!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
@@ -476,16 +487,19 @@ fn transform_for_direct_spawn_windows_materializes_inner_helper() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
|
||||
@@ -49,6 +49,7 @@ pub fn normalize_additional_permissions(
|
||||
let normalized_entry = FileSystemSandboxEntry {
|
||||
path,
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
};
|
||||
if !entries.contains(&normalized_entry) {
|
||||
entries.push(normalized_entry);
|
||||
@@ -355,6 +356,7 @@ fn materialize_cwd_dependent_entry(
|
||||
.map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
})
|
||||
.unwrap_or_else(|| entry.clone()),
|
||||
FileSystemPath::GlobPattern { pattern } => FileSystemSandboxEntry {
|
||||
@@ -364,6 +366,7 @@ fn materialize_cwd_dependent_entry(
|
||||
.into_owned(),
|
||||
},
|
||||
access: entry.access,
|
||||
missing_path_behavior: entry.missing_path_behavior,
|
||||
},
|
||||
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => entry.clone(),
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ fn full_access_restricted_policy_skips_platform_sandbox_when_network_is_enabled(
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
assert_eq!(
|
||||
@@ -55,10 +56,12 @@ fn root_write_policy_with_carveouts_still_uses_platform_sandbox() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: blocked },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -79,6 +82,7 @@ fn full_access_restricted_policy_still_uses_platform_sandbox_for_restricted_netw
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]);
|
||||
|
||||
assert_eq!(
|
||||
@@ -166,6 +170,7 @@ fn normalize_additional_permissions_rejects_glob_read_grants() {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -188,6 +193,7 @@ fn normalize_additional_permissions_preserves_deny_globs() {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
}),
|
||||
@@ -204,6 +210,7 @@ fn normalize_additional_permissions_preserves_deny_globs() {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
}),
|
||||
@@ -302,6 +309,7 @@ fn intersect_permission_profiles_accepts_child_path_granted_for_requested_cwd()
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -335,6 +343,7 @@ fn intersect_permission_profiles_materializes_cwd_grant_for_reuse() {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -386,10 +395,12 @@ fn intersect_permission_profiles_deduplicates_materialized_grants() {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -422,12 +433,14 @@ fn intersect_permission_profiles_materializes_cwd_deny_entries() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -445,10 +458,12 @@ fn intersect_permission_profiles_materializes_cwd_deny_entries() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: request_cwd },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -477,10 +492,12 @@ fn intersect_permission_profiles_drops_deny_entries_without_filesystem_grants()
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: secret },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -515,12 +532,14 @@ fn intersect_permission_profiles_rejects_concrete_grants_matched_by_requested_de
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
@@ -553,12 +572,14 @@ fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let deny_env_files = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let permissions = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
@@ -581,12 +602,14 @@ fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
|
||||
path: request_cwd.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: request_cwd.join("**/*.env").to_string_lossy().into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
@@ -632,6 +655,7 @@ fn intersect_permission_profiles_drops_broader_cwd_grant_for_requested_child_pat
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
@@ -652,12 +676,14 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let deny_env_files = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let requested = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
@@ -690,6 +716,7 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(4),
|
||||
@@ -707,12 +734,14 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let deny_env_files = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let requested = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
@@ -745,6 +774,7 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -770,12 +800,14 @@ fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roo
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]),
|
||||
&FileSystemPermissions::from_read_write_roots(
|
||||
@@ -788,6 +820,7 @@ fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roo
|
||||
merged_policy.entries.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: denied_path },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}),
|
||||
true
|
||||
);
|
||||
@@ -795,6 +828,7 @@ fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roo
|
||||
merged_policy.entries.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: allowed_path },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
}),
|
||||
true
|
||||
);
|
||||
@@ -807,6 +841,7 @@ fn merge_file_system_policy_with_additional_permissions_carries_bounded_glob_sca
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
};
|
||||
let merged_policy = merge_file_system_policy_with_additional_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
@@ -814,6 +849,7 @@ fn merge_file_system_policy_with_additional_permissions_carries_bounded_glob_sca
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}]),
|
||||
&FileSystemPermissions {
|
||||
entries: vec![deny_env_files.clone()],
|
||||
@@ -828,6 +864,7 @@ fn merge_file_system_policy_with_additional_permissions_carries_bounded_glob_sca
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
deny_env_files,
|
||||
]);
|
||||
@@ -850,10 +887,12 @@ fn effective_file_system_sandbox_policy_returns_base_policy_without_additional_p
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: denied_path },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -878,12 +917,14 @@ fn effective_file_system_sandbox_policy_merges_additional_write_roots() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
let additional_permissions = PermissionProfile {
|
||||
@@ -901,6 +942,7 @@ fn effective_file_system_sandbox_policy_merges_additional_write_roots() {
|
||||
effective_policy.entries.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: denied_path },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}),
|
||||
true
|
||||
);
|
||||
@@ -908,6 +950,7 @@ fn effective_file_system_sandbox_policy_merges_additional_write_roots() {
|
||||
effective_policy.entries.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: allowed_path },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
@@ -193,10 +193,12 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: unreadable },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -299,10 +301,12 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root },
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: unreadable },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -411,6 +415,7 @@ fn unreadable_glob_policy_includes_canonicalized_static_prefix() {
|
||||
policy.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern { pattern },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
|
||||
let seatbelt_policy = build_seatbelt_unreadable_glob_policy(&policy, temp_dir.path());
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
|
||||
let needs_direct_runtime_enforcement = file_system_sandbox_policy
|
||||
@@ -107,6 +107,11 @@ pub fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
));
|
||||
}
|
||||
|
||||
// Windows protects existing metadata paths through the legacy writable root
|
||||
// projection. Do not turn skip-missing entries into newly-created
|
||||
// deny-write sentinels.
|
||||
file_system_sandbox_policy.remove_skip_missing_path_entries();
|
||||
|
||||
// The restricted-token backend can still enforce split write restrictions,
|
||||
// but its WRITE_RESTRICTED token does not make capability SID deny-read ACEs
|
||||
// participate in read access checks. Read restrictions therefore require the
|
||||
@@ -221,7 +226,7 @@ pub fn resolve_windows_elevated_filesystem_overrides(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
|
||||
if !permission_profile_supports_windows_restricted_token_sandbox(permission_profile) {
|
||||
@@ -232,6 +237,11 @@ pub fn resolve_windows_elevated_filesystem_overrides(
|
||||
));
|
||||
}
|
||||
|
||||
// Windows protects existing metadata paths through the legacy writable root
|
||||
// projection. Do not turn skip-missing entries into newly-created
|
||||
// deny-write sentinels.
|
||||
file_system_sandbox_policy.remove_skip_missing_path_entries();
|
||||
|
||||
let additional_deny_read_paths = codex_windows_sandbox::resolve_windows_deny_read_paths(
|
||||
&file_system_sandbox_policy,
|
||||
sandbox_policy_cwd,
|
||||
@@ -248,6 +258,12 @@ pub fn resolve_windows_elevated_filesystem_overrides(
|
||||
|
||||
let needs_direct_runtime_enforcement = file_system_sandbox_policy
|
||||
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd);
|
||||
let has_explicit_write_carveouts = split_writable_roots.iter().any(|writable_root| {
|
||||
writable_root.read_only_subpaths.iter().any(|path| {
|
||||
file_system_sandbox_policy
|
||||
.has_explicit_non_write_entry_for_path_with_cwd(path.as_path(), sandbox_policy_cwd)
|
||||
})
|
||||
});
|
||||
let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path);
|
||||
let legacy_projection = compatibility_sandbox_policy_for_permission_profile(
|
||||
permission_profile,
|
||||
@@ -288,7 +304,9 @@ pub fn resolve_windows_elevated_filesystem_overrides(
|
||||
Some(split_root_paths)
|
||||
};
|
||||
|
||||
let additional_deny_write_paths = if needs_direct_runtime_enforcement {
|
||||
let additional_deny_write_paths = if needs_direct_runtime_enforcement
|
||||
|| has_explicit_write_carveouts
|
||||
{
|
||||
let mut deny_paths = BTreeSet::new();
|
||||
for writable_root in &split_writable_roots {
|
||||
let writable_root_path = normalize_path(writable_root.root.to_path_buf());
|
||||
@@ -309,7 +327,12 @@ pub fn resolve_windows_elevated_filesystem_overrides(
|
||||
== read_only_subpath_suffix
|
||||
})
|
||||
});
|
||||
if !already_denied_by_legacy {
|
||||
let explicitly_configured = file_system_sandbox_policy
|
||||
.has_explicit_non_write_entry_for_path_with_cwd(
|
||||
read_only_subpath.as_path(),
|
||||
sandbox_policy_cwd,
|
||||
);
|
||||
if explicitly_configured || !already_denied_by_legacy {
|
||||
deny_paths.insert(normalize_path(read_only_subpath.to_path_buf()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,12 +112,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: "/tmp/writable".try_into().expect("absolute path"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -300,12 +300,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -2230,10 +2230,12 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: extra_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -2258,12 +2260,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots { subpath: None },
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -253,12 +253,14 @@ async fn submission_includes_configured_active_permission_profile() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "/home/user/project/secrets/**".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -481,12 +481,14 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/.secret".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -20,28 +20,33 @@ fn app_server_workspace_write_profile(extra_root: AbsolutePathBuf) -> Permission
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots { subpath: None },
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: extra_root },
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -359,12 +364,14 @@ async fn preset_matching_does_not_treat_non_cwd_writable_profile_as_read_only()
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: test_path_buf("/tmp/writable").abs(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -65,12 +65,14 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: extra_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -101,24 +101,28 @@ fn app_server_workspace_write_profile(network_enabled: bool) -> PermissionProfil
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots { subpath: None },
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn resolve_windows_deny_read_paths(
|
||||
pattern: pattern.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
@@ -206,6 +207,7 @@ mod tests {
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern { pattern },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +217,7 @@ mod tests {
|
||||
path: AbsolutePathBuf::from_absolute_path(path).expect("absolute path"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ impl ResolvedWindowsSandboxPermissions {
|
||||
self.file_system
|
||||
.entries
|
||||
.iter()
|
||||
.any(|FileSystemSandboxEntry { path, access }| {
|
||||
.any(|FileSystemSandboxEntry { path, access, .. }| {
|
||||
matches!(
|
||||
path,
|
||||
FileSystemPath::Special {
|
||||
@@ -258,6 +258,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
@@ -298,18 +299,21 @@ mod tests {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".git".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
@@ -332,24 +336,28 @@ mod tests {
|
||||
path: first.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: second.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: first.join(".git"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: second.join(".git"),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
@@ -361,6 +369,7 @@ mod tests {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
@@ -372,6 +381,7 @@ mod tests {
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
},
|
||||
])
|
||||
);
|
||||
@@ -455,6 +465,7 @@ mod tests {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
missing_path_behavior: None,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user