Enforce managed deny-read rules in the Windows sandbox (#38660)

## Why

Windows sandbox requests must preserve managed filesystem deny rules across every execution path and setup refresh. Unsupported policies should fail closed instead of allowing a command to run without the requested protection.

## What changed

- Resolve Windows filesystem overrides while constructing each sandbox execution request so both `shell_command` and `exec_command` enforce exact-path and glob deny-read entries.
- Carry resolved deny-read paths into Windows sandbox setup refreshes, including workspace-relative entries.
- Reject unelevated restricted-token requests that cannot enforce deny-read rules.
- Reject recursive globs rooted at a filesystem root unless `glob_scan_max_depth` bounds their expansion.

## Testing

Added coverage for deny-read enforcement through both command runtimes, setup refresh resolution, restricted-token failure, and bounded root-level glob scans.

GitOrigin-RevId: 33dfa4d1a45b14850cbf58f9173717e3e707d9bf
This commit is contained in:
johnl-oai
2026-08-14 22:28:33 +00:00
committed by copyberry
parent 5186e2ccc3
commit 848cbad7f4
8 changed files with 486 additions and 47 deletions
+9 -31
View File
@@ -45,10 +45,13 @@ pub(crate) use codex_sandboxing::is_likely_sandbox_denied;
#[cfg(test)]
use codex_sandboxing::permission_profile_supports_windows_restricted_token_sandbox;
use codex_sandboxing::record_filesystem_sandbox_violation;
#[cfg(test)]
use codex_sandboxing::resolve_windows_elevated_filesystem_overrides;
#[cfg(test)]
use codex_sandboxing::resolve_windows_restricted_token_filesystem_overrides;
#[cfg(test)]
use codex_sandboxing::unsupported_windows_restricted_token_sandbox_reason;
#[cfg(any(test, target_os = "windows"))]
use codex_sandboxing::windows_sandbox_uses_elevated_backend;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
@@ -379,7 +382,7 @@ pub fn build_exec_request(
expiration,
capture_policy,
};
let mut exec_req = manager
let request = manager
.transform(SandboxTransformRequest {
command,
permissions: permission_profile,
@@ -393,38 +396,13 @@ pub fn build_exec_request(
windows_sandbox_level,
windows_sandbox_private_desktop,
})
.map(|request| {
let windows_sandbox_workspace_roots = if windows_sandbox_workspace_roots.is_empty() {
vec![sandbox_cwd.clone()]
} else {
windows_sandbox_workspace_roots.to_vec()
};
ExecRequest::from_sandbox_exec_request(
request,
options,
windows_sandbox_workspace_roots,
)
})
.map_err(CodexErr::from)?;
let use_windows_elevated_backend =
windows_sandbox_uses_elevated_backend(exec_req.windows_sandbox_level);
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
exec_req.sandbox,
&exec_req.permission_profile,
sandbox_cwd,
use_windows_elevated_backend,
)
let windows_sandbox_workspace_roots = if windows_sandbox_workspace_roots.is_empty() {
vec![sandbox_cwd.clone()]
} else {
resolve_windows_restricted_token_filesystem_overrides(
exec_req.sandbox,
&exec_req.permission_profile,
sandbox_cwd,
exec_req.windows_sandbox_level,
)
}
.map_err(CodexErr::UnsupportedOperation)?;
Ok(exec_req)
windows_sandbox_workspace_roots.to_vec()
};
ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_workspace_roots)
}
pub(crate) async fn execute_exec_request(
+39 -4
View File
@@ -19,14 +19,19 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::error::CodexErr;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::PermissionProfile;
pub use codex_protocol::models::SandboxPermissions;
use codex_sandboxing::SandboxExecRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::WindowsSandboxFilesystemOverrides;
use codex_sandboxing::resolve_windows_elevated_filesystem_overrides;
use codex_sandboxing::resolve_windows_restricted_token_filesystem_overrides;
use codex_sandboxing::windows_sandbox_uses_elevated_backend;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use codex_utils_string::truncate_middle_with_token_budget;
use std::collections::HashMap;
#[derive(Debug)]
@@ -112,7 +117,7 @@ impl ExecRequest {
request: SandboxExecRequest,
options: ExecOptions,
windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
) -> Self {
) -> Result<Self, CodexErr> {
let SandboxExecRequest {
command,
cwd,
@@ -131,6 +136,36 @@ impl ExecRequest {
expiration,
capture_policy,
} = options;
let windows_sandbox_filesystem_overrides = if sandbox == SandboxType::WindowsRestrictedToken
{
let sandbox_policy_cwd = windows_sandbox_policy_cwd
.to_abs_path()
.map_err(|err| CodexErr::InvalidRequest(format!("invalid sandbox cwd: {err}")))?;
let use_windows_elevated_backend =
windows_sandbox_uses_elevated_backend(windows_sandbox_level);
if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
sandbox,
&permission_profile,
&sandbox_policy_cwd,
use_windows_elevated_backend,
)
} else {
resolve_windows_restricted_token_filesystem_overrides(
sandbox,
&permission_profile,
&sandbox_policy_cwd,
windows_sandbox_level,
)
}
.map_err(|error| {
CodexErr::UnsupportedOperation(
truncate_middle_with_token_budget(&error, /*max_tokens*/ 900).0,
)
})?
} else {
None
};
let network_sandbox_policy = permission_profile.network_sandbox_policy();
if !network_sandbox_policy.is_enabled() {
env.insert(
@@ -142,7 +177,7 @@ impl ExecRequest {
if sandbox == SandboxType::MacosSeatbelt {
env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
}
Self {
Ok(Self {
command,
cwd,
env,
@@ -157,13 +192,13 @@ impl ExecRequest {
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
windows_sandbox_filesystem_overrides: None,
windows_sandbox_filesystem_overrides,
arg0,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
exec_server_network_proxy: None,
}
})
}
}
@@ -981,7 +981,7 @@ impl CoreShellCommandExecutor {
exec_request,
options,
self.windows_sandbox_workspace_roots.clone(),
);
)?;
if let Some(network) = exec_request.network.as_ref() {
network
.apply_to_env_for_optional_environment(
+6 -7
View File
@@ -453,11 +453,7 @@ impl<'a> SandboxAttempt<'a> {
.iter()
.map(PathUri::to_abs_path)
.collect::<std::io::Result<Vec<_>>>()?;
Ok(crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
workspace_roots,
))
crate::sandboxing::ExecRequest::from_sandbox_exec_request(request, options, workspace_roots)
}
pub fn env_for_exec_server(
@@ -487,8 +483,11 @@ impl<'a> SandboxAttempt<'a> {
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
.map_err(CodexErr::from)?;
let mut exec_request =
crate::sandboxing::ExecRequest::from_sandbox_exec_request(request, options, Vec::new());
let mut exec_request = crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
Vec::new(),
)?;
exec_request.exec_server_managed_network = managed_network;
if self.sandbox_requested {
exec_request.exec_server_sandbox = Some(FileSystemSandboxContext {
+103
View File
@@ -5,6 +5,8 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
@@ -202,6 +204,107 @@ fn deny_read_blocks_explicit_escalation_and_policy_bypass() {
);
}
#[test]
fn windows_sandbox_env_preserves_denied_reads_or_rejects_unsupported_backend() {
let temp_dir = tempfile::TempDir::new().expect("create sandbox workspace");
let cwd = AbsolutePathBuf::from_absolute_path(
dunce::canonicalize(temp_dir.path()).expect("canonicalize sandbox workspace"),
)
.expect("absolute sandbox workspace");
let denied_path = cwd.join("blocked");
std::fs::create_dir_all(denied_path.as_path()).expect("create denied directory");
let denied_path = AbsolutePathBuf::from_absolute_path(
dunce::canonicalize(denied_path.as_path()).expect("canonicalize denied directory"),
)
.expect("absolute denied directory");
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
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: denied_path.clone(),
},
access: FileSystemAccessMode::Deny,
missing_path_behavior: None,
},
]);
let permissions = codex_protocol::models::PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
);
let cwd_uri = PathUri::from_abs_path(&cwd);
let manager = SandboxManager::new();
let mut attempt = SandboxAttempt {
sandbox: SandboxType::WindowsRestrictedToken,
sandbox_requested: true,
permissions: &permissions,
exec_server_permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
network_proxy: None,
};
let command = || SandboxCommand {
program: "cmd.exe".into(),
args: vec!["/C".to_string(), "echo sandboxed".to_string()],
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: None,
};
let options = || crate::sandboxing::ExecOptions {
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
};
let request = attempt
.env_for(
command(),
options(),
/*network*/ None,
/*environment_id*/ None,
)
.expect("prepare elevated Windows sandbox request");
let overrides = request
.windows_sandbox_filesystem_overrides
.expect("elevated Windows sandbox should preserve deny-read overrides");
assert_eq!(overrides.additional_deny_read_paths, vec![denied_path]);
assert_eq!(request.windows_sandbox_workspace_roots, vec![cwd]);
attempt.windows_sandbox_level =
codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken;
let error = attempt
.env_for(
command(),
options(),
/*network*/ None,
/*environment_id*/ None,
)
.expect_err("restricted-token Windows sandbox cannot enforce deny-read restrictions");
assert_eq!(
error.to_string(),
"unsupported operation: windows unelevated restricted-token sandbox cannot enforce deny-read restrictions directly; refusing to run unsandboxed"
);
}
#[test]
fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
let cwd: AbsolutePathBuf = std::env::current_dir()
@@ -4,6 +4,7 @@ use codex_core::exec::ExecParams;
use codex_core::exec::process_exec_tool_call;
use codex_core::sandboxing::SandboxPermissions;
use codex_core::windows_sandbox::sandbox_setup_is_complete;
use codex_features::Feature;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::PermissionProfile;
@@ -14,7 +15,16 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use core_test_support::PathExt;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::test_codex::TestCodexHarness;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use serde_json::json;
use serial_test::serial;
use std::collections::HashMap;
use std::ffi::OsString;
@@ -383,3 +393,156 @@ async fn windows_elevated_enforces_deny_read_and_protects_setup_marker() -> anyh
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[serial(codex_home)]
async fn windows_elevated_shell_and_unified_exec_enforce_managed_deny_reads() -> anyhow::Result<()>
{
let codex_home =
codex_home_for_windows_sandbox_test("windows-elevated-tool-runtime-deny-read-codex-home")?;
let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.path().as_os_str());
stage_windows_sandbox_helpers()?;
let configured_codex_home = dunce::canonicalize(codex_home.path())?.abs();
let builder = test_codex()
.with_windows_cmd_shell()
.with_config(move |config| {
config.codex_home = configured_codex_home;
config.set_windows_elevated_sandbox_enabled(true);
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow unified exec");
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
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: config.cwd.join("exact-secret.txt"),
},
access: FileSystemAccessMode::Deny,
missing_path_behavior: None,
},
]);
config
.permissions
.set_permission_profile(PermissionProfile::from_runtime_permissions(
&file_system_sandbox_policy,
NetworkSandboxPolicy::Restricted,
))
.expect("set managed deny-read permission profile");
});
let harness = TestCodexHarness::with_builder(builder).await?;
harness
.write_file("secret.env", "glob secret should remain private\n")
.await?;
harness
.write_file("exact-secret.txt", "exact secret should remain private\n")
.await?;
harness.write_file("public.txt", "public ok\n").await?;
let command = concat!(
"(type secret.env 1>NUL 2>NUL && echo GLOB-READ || echo GLOB-DENIED) & ",
"(type exact-secret.txt 1>NUL 2>NUL && echo EXACT-READ || echo EXACT-DENIED) & ",
"type public.txt"
);
let shell_call_id = "windows-managed-deny-read-shell-command";
let unified_call_id = "windows-managed-deny-read-exec-command";
let shell_args = json!({
"command": command,
"timeout_ms": 30_000,
"login": false,
});
let unified_args = json!({
"cmd": command,
"yield_time_ms": 30_000,
"tty": false,
"login": false,
});
mount_sse_sequence(
harness.server(),
vec![
sse(vec![
ev_response_created("resp-windows-shell-deny-read"),
ev_function_call(
shell_call_id,
"shell_command",
&serde_json::to_string(&shell_args)?,
),
ev_completed("resp-windows-shell-deny-read"),
]),
sse(vec![
ev_response_created("resp-windows-unified-deny-read"),
ev_function_call(
unified_call_id,
"exec_command",
&serde_json::to_string(&unified_args)?,
),
ev_completed("resp-windows-unified-deny-read"),
]),
sse(vec![
ev_assistant_message("msg-windows-deny-read", "done"),
ev_completed("resp-windows-deny-read-complete"),
]),
],
)
.await;
let permission_profile = harness
.test()
.config
.permissions
.effective_permission_profile();
harness
.submit_with_permission_profile("read the sandbox fixtures", permission_profile)
.await?;
for (tool_name, call_id) in [
("shell_command", shell_call_id),
("exec_command", unified_call_id),
] {
let output = harness.function_call_stdout(call_id).await;
assert!(
output.contains("GLOB-DENIED"),
"{tool_name} should reject glob-denied reads: {output:?}"
);
assert!(
output.contains("EXACT-DENIED"),
"{tool_name} should reject exact-path-denied reads: {output:?}"
);
assert!(
output.contains("public ok"),
"{tool_name} should preserve allowed reads: {output:?}"
);
assert!(
!output.contains("GLOB-READ") && !output.contains("glob secret"),
"{tool_name} leaked glob-denied file contents: {output:?}"
);
assert!(
!output.contains("EXACT-READ") && !output.contains("exact secret"),
"{tool_name} leaked exact-path-denied file contents: {output:?}"
);
}
Ok(())
}
@@ -52,9 +52,21 @@ pub fn resolve_windows_deny_read_paths(
return Ok(paths);
};
for pattern in unreadable_globs {
let scan_plans = unreadable_globs
.iter()
.map(|pattern| {
let scan_plan = glob_scan_plan(pattern, file_system_sandbox_policy.glob_scan_max_depth);
if scan_plan.max_depth.is_none() && scan_plan.root.parent().is_none() {
return Err(format!(
"unreadable glob `{pattern}` cannot be safely expanded from a filesystem root without `glob_scan_max_depth`; configure `glob_scan_max_depth` or use a non-root directory prefix"
));
}
Ok(scan_plan)
})
.collect::<Result<Vec<_>, String>>()?;
for scan_plan in scan_plans {
let mut seen_scan_dirs = HashSet::new();
let scan_plan = glob_scan_plan(&pattern, file_system_sandbox_policy.glob_scan_max_depth);
collect_existing_glob_matches(
&scan_plan.root,
&matcher,
@@ -269,6 +281,35 @@ mod tests {
);
}
#[test]
fn root_recursive_globs_without_depth_fail_before_expansion() {
let tmp = TempDir::new().expect("tempdir");
let cwd = AbsolutePathBuf::from_absolute_path(tmp.path()).expect("absolute cwd");
let root = cwd.as_path().ancestors().last().expect("filesystem root");
let pattern = root.join("**").join("*.env").display().to_string();
let policy =
FileSystemSandboxPolicy::restricted(vec![unreadable_glob_entry(pattern.clone())]);
assert_eq!(
resolve_windows_deny_read_paths(&policy, &cwd).expect_err("unbounded root glob"),
format!(
"unreadable glob `{pattern}` cannot be safely expanded from a filesystem root without `glob_scan_max_depth`; configure `glob_scan_max_depth` or use a non-root directory prefix"
)
);
}
#[test]
fn configured_depth_bounds_root_recursive_glob_scans() {
let tmp = TempDir::new().expect("tempdir");
let cwd = AbsolutePathBuf::from_absolute_path(tmp.path()).expect("absolute cwd");
let root = cwd.as_path().ancestors().last().expect("filesystem root");
let pattern = root.join("**").join("*.env").display().to_string();
let scan_plan = glob_scan_plan(&pattern, Some(2));
assert_eq!(scan_plan.root, root);
assert_eq!(scan_plan.max_depth, Some(2));
}
#[test]
fn exact_missing_paths_are_preserved() {
let tmp = TempDir::new().expect("tempdir");
+122 -2
View File
@@ -16,6 +16,7 @@ use std::sync::OnceLock;
use crate::allow::AllowDenyPaths;
use crate::allow::compute_allow_paths_for_permissions;
use crate::deny_read_resolver::resolve_windows_deny_read_paths;
use crate::helper_materialization::bundled_executable_path_for_exe;
use crate::helper_materialization::helper_bin_dir;
use crate::identity::sandbox_setup_is_complete;
@@ -233,6 +234,8 @@ pub fn run_setup_refresh(
else {
return Ok(());
};
let deny_read_paths =
setup_refresh_deny_read_paths(permission_profile, workspace_roots, command_cwd)?;
run_setup_refresh_inner(
SandboxSetupRequest {
permissions: &permissions,
@@ -241,7 +244,10 @@ pub fn run_setup_refresh(
codex_home,
proxy_enforced,
},
SetupRootOverrides::default(),
SetupRootOverrides {
deny_read_paths: Some(deny_read_paths),
..SetupRootOverrides::default()
},
/*offline_proxy_settings_override*/ None,
)
}
@@ -271,6 +277,8 @@ pub fn run_setup_refresh_with_extra_read_roots(
else {
return Ok(());
};
let deny_read_paths =
setup_refresh_deny_read_paths(permission_profile, workspace_roots, command_cwd)?;
let mut read_roots = gather_read_roots(command_cwd, &permissions, env_map, codex_home);
read_roots.extend(extra_read_roots);
run_setup_refresh_inner(
@@ -285,13 +293,32 @@ pub fn run_setup_refresh_with_extra_read_roots(
read_roots: Some(read_roots),
read_roots_include_platform_defaults: false,
write_roots: Some(Vec::new()),
deny_read_paths: None,
deny_read_paths: Some(deny_read_paths),
deny_write_paths: None,
},
/*offline_proxy_settings_override*/ None,
)
}
fn setup_refresh_deny_read_paths(
permission_profile: &PermissionProfile,
workspace_roots: &[AbsolutePathBuf],
command_cwd: &Path,
) -> Result<Vec<PathBuf>> {
let (mut file_system, _) = permission_profile.to_runtime_permissions();
file_system.remove_skip_missing_path_entries();
let file_system = file_system.materialize_project_roots_with_workspace_roots(workspace_roots);
let command_cwd = AbsolutePathBuf::from_absolute_path(command_cwd)?;
resolve_windows_deny_read_paths(&file_system, &command_cwd)
.map(|paths| {
paths
.into_iter()
.map(AbsolutePathBuf::into_path_buf)
.collect()
})
.map_err(|err| anyhow!(err))
}
fn run_setup_refresh_inner(
request: SandboxSetupRequest<'_>,
overrides: SetupRootOverrides,
@@ -1298,8 +1325,14 @@ mod tests {
use crate::setup_error::SetupErrorReport;
use crate::setup_error::extract_failure;
use crate::setup_error::write_setup_error_report;
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::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::permissions::project_roots_glob_pattern;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@@ -1556,6 +1589,93 @@ mod tests {
}
}
#[test]
fn setup_refresh_preserves_workspace_scoped_deny_read_paths() {
let tmp = TempDir::new().expect("tempdir");
let workspace_root = tmp.path().join("workspace");
let command_cwd = tmp.path().join("command-cwd");
let denied_glob_match = workspace_root.join("app").join("secret.env");
fs::create_dir_all(&command_cwd).expect("create command cwd");
fs::create_dir_all(denied_glob_match.parent().expect("glob parent"))
.expect("create glob parent");
fs::write(&denied_glob_match, "secret").expect("write denied glob match");
let permission_profile = PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
),
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(Some(
"private".to_string(),
)),
},
FileSystemAccessMode::Deny,
),
FileSystemSandboxEntry::new(
FileSystemPath::GlobPattern {
pattern: project_roots_glob_pattern(Path::new("**/*.env")),
},
FileSystemAccessMode::Deny,
),
],
glob_scan_max_depth: None,
},
network: NetworkSandboxPolicy::Restricted,
};
let deny_read_paths = super::setup_refresh_deny_read_paths(
&permission_profile,
workspace_roots_for(&workspace_root).as_slice(),
&command_cwd,
)
.expect("resolve refresh deny-read paths");
assert_eq!(
deny_read_paths.into_iter().collect::<HashSet<_>>(),
[
dunce::canonicalize(&workspace_root)
.expect("canonicalize workspace root")
.join("private"),
denied_glob_match,
]
.into_iter()
.collect()
);
}
#[test]
fn setup_refresh_rejects_invalid_deny_read_globs() {
let tmp = TempDir::new().expect("tempdir");
let workspace_root = tmp.path().join("workspace");
fs::create_dir_all(&workspace_root).expect("create workspace");
let permission_profile = PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![FileSystemSandboxEntry::new(
FileSystemPath::GlobPattern {
pattern: project_roots_glob_pattern(Path::new("[z-a]")),
},
FileSystemAccessMode::Deny,
)],
glob_scan_max_depth: None,
},
network: NetworkSandboxPolicy::Restricted,
};
let err = super::setup_refresh_deny_read_paths(
&permission_profile,
workspace_roots_for(&workspace_root).as_slice(),
&workspace_root,
)
.expect_err("invalid deny-read glob");
assert!(err.to_string().contains("invalid deny-read glob pattern"));
}
#[test]
fn loopback_proxy_url_parsing_supports_common_forms() {
assert_eq!(