use super::sandbox::spawn_command_under_sandbox; use codex_core::spawn::StdioPolicy; 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::NetworkSandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use std::collections::HashMap; use std::fs; use std::os::unix::fs::symlink; use std::process::Output; use std::time::Duration; use tempfile::TempDir; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn codex_home_symlink_opt_out_uses_loaded_user_config() -> anyhow::Result<()> { use core_test_support::responses; use core_test_support::test_codex_exec::test_codex_exec; use serde_json::json; core_test_support::skip_if_sandbox!(Ok(())); for (home_env, root_alias, enabled, ignore_user_config) in [ ("user-home", Some("user-home"), true, false), ("home-link/../.codex", Some(".codex"), false, false), ("home-link/../.codex", None, true, false), ("user-home", Some("user-home"), true, true), ] { let test = test_codex_exec(); let home = test.home_path().join(".codex"); let project_home = test.cwd_path().join(".codex"); let child = test.home_path().join("child"); for directory in [&home, &project_home, &child] { fs::create_dir(directory)?; } symlink(&home, test.cwd_path().join("user-home"))?; symlink(&child, test.cwd_path().join("home-link"))?; fs::write( home.join("config.toml"), format!("allow_symlinked_codex_home = {enabled}\n"), )?; // The checkout must not enable the opt-out, even when `..` follows a symlink. fs::write( project_home.join("config.toml"), "allow_symlinked_codex_home = true\n", )?; let target = TempDir::new()?; for root in [&home, &project_home] { symlink(target.path(), root.join("visualizations"))?; } let writable_home = root_alias.map_or_else(|| home.clone(), |alias| test.cwd_path().join(alias)); let visualizations = writable_home.join("visualizations"); let shell_args = json!({ "cmd": "printf shell > shell.txt", "workdir": visualizations, "shell": "/bin/sh", "login": false, }) .to_string(); let patch = format!( "*** Begin Patch\n*** Add File: {}/patch.txt\n+patched\n*** End Patch", visualizations.display() ); let server = responses::start_mock_server().await; let should_run = enabled && !ignore_user_config; let mock = responses::mount_sse_sequence( &server, vec![ responses::sse(vec![ responses::ev_function_call("shell", "exec_command", &shell_args), responses::ev_apply_patch_custom_tool_call("patch", &patch), responses::ev_completed("response-1"), ]), responses::sse(vec![responses::ev_completed("response-2")]), ], ) .await; let mut command = test.cmd_with_server(&server); command .env_remove("CODEX_API_KEY") .env_remove("OPENAI_API_KEY") .env_remove("CODEX_ACCESS_TOKEN") .env("CODEX_HOME", home_env) .args(["--skip-git-repo-check", "--sandbox", "workspace-write", "--add-dir"]) .arg(&visualizations) .args(["-c", "approvals_reviewer=\"user\"", "-c", "features.shell_snapshot_v2=false"]) // Session overrides cannot authorize the host's exception either. .args(["-c", "allow_symlinked_codex_home=true"]) .args(["-c", "model_provider=\"test\"", "-c"]) .arg(format!( "model_providers.test={{name=\"test\",base_url={:?},wire_api=\"responses\",requires_openai_auth=false,supports_websockets=false}}", format!("{}/v1", server.uri()) )) .arg("write the files"); if ignore_user_config { command.arg("--ignore-user-config"); } let output = command.output()?; assert!(output.status.success(), "{output:?}"); assert_eq!(mock.requests().len(), 2); if should_run { assert_eq!(fs::read(target.path().join("shell.txt"))?, b"shell"); assert_eq!(fs::read(target.path().join("patch.txt"))?, b"patched\n"); } else { let shell_output = mock .function_call_output_text("shell") .expect("shell tool output"); assert!( shell_output.contains("symlinked writable roots are not supported"), "{shell_output}" ); assert!(!target.path().join("shell.txt").exists()); assert!(!target.path().join("patch.txt").exists()); } } Ok(()) } fn workspace_fixture() -> (TempDir, AbsolutePathBuf) { let temp_dir = TempDir::new().expect("create temporary directory"); let workspace = temp_dir.path().join("workspace"); fs::create_dir(&workspace).expect("create workspace"); let workspace = AbsolutePathBuf::from_absolute_path( workspace.canonicalize().expect("canonicalize workspace"), ) .expect("workspace is absolute"); (temp_dir, workspace) } fn permission_profile( workspace: &AbsolutePathBuf, restricted_paths: Vec<(FileSystemPath, FileSystemAccessMode)>, ) -> PermissionProfile { let mut file_system_policy = FileSystemSandboxPolicy::read_only(); file_system_policy.entries.push(FileSystemSandboxEntry::new( workspace.clone().into(), FileSystemAccessMode::Write, )); file_system_policy.entries.extend( restricted_paths .into_iter() .map(|(path, access)| FileSystemSandboxEntry::new(path, access)), ); PermissionProfile::from_runtime_permissions( &file_system_policy, NetworkSandboxPolicy::Restricted, ) } fn denied_glob_profile(workspace: &AbsolutePathBuf, pattern: String) -> PermissionProfile { permission_profile( workspace, vec![( FileSystemPath::GlobPattern { pattern }, FileSystemAccessMode::Deny, )], ) } async fn run_sandboxed_command( command: Vec, workspace: &AbsolutePathBuf, permission_profile: &PermissionProfile, ) -> Output { spawn_command_under_sandbox( command, workspace.clone(), permission_profile, workspace, StdioPolicy::RedirectForShellTool, HashMap::new(), ) .await .expect("spawn command under production Seatbelt policy") .wait_with_output() .await .expect("wait for sandboxed command") } async fn run_path_command( executable: &str, path: &AbsolutePathBuf, workspace: &AbsolutePathBuf, profile: &PermissionProfile, ) -> Output { run_sandboxed_command( vec![executable.to_string(), path.display().to_string()], workspace, profile, ) .await } async fn assert_denied_read( path: &AbsolutePathBuf, workspace: &AbsolutePathBuf, profile: &PermissionProfile, ) { let output = run_path_command("/bin/cat", path, workspace, profile).await; assert!( !output.status.success(), "Seatbelt allowed reading {}: {output:?}", path.display() ); assert!(output.stdout.is_empty(), "protected contents leaked"); } fn assert_file_contents(path: &AbsolutePathBuf, contents: &str) { assert_eq!(fs::read_to_string(path).expect("read fixture"), contents); } async fn run_shell_command( script: &str, path: &AbsolutePathBuf, workspace: &AbsolutePathBuf, profile: &PermissionProfile, ) -> Output { run_sandboxed_command( vec![ "/bin/sh".to_string(), "-c".to_string(), script.to_string(), "sh".to_string(), path.display().to_string(), ], workspace, profile, ) .await } async fn run_after_policy_applied( operation: &str, target: &AbsolutePathBuf, extra_args: Vec, workspace: &AbsolutePathBuf, profile: &PermissionProfile, setup: impl FnOnce(), ) -> Output { let label = target .as_path() .file_name() .expect("filename") .to_string_lossy(); let ready = workspace.join(format!("{label}.ready.txt")); let proceed = workspace.join(format!("{label}.proceed.txt")); let script = format!( "printf ready > \"$2\"; while [ ! -e \"$3\" ]; do /bin/sleep 0.01; done; {operation}" ); let mut command = vec![ "/bin/sh".to_string(), "-c".to_string(), script, "sh".to_string(), target.display().to_string(), ready.display().to_string(), proceed.display().to_string(), ]; command.extend(extra_args); let child = spawn_command_under_sandbox( command, workspace.clone(), profile, workspace, StdioPolicy::RedirectForShellTool, HashMap::new(), ) .await .expect("spawn command before creating protected fixture"); tokio::time::timeout(Duration::from_secs(5), async { while !ready.as_path().exists() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await .expect("sandboxed command did not signal policy application"); setup(); fs::write(&proceed, "proceed").expect("release sandboxed command"); child .wait_with_output() .await .expect("wait for sandboxed command") } #[tokio::test] async fn seatbelt_enforces_brace_alternation_and_escaped_deny_globs() { core_test_support::skip_if_sandbox!(); let (temp_dir, workspace) = workspace_fixture(); let dotenv = workspace.join(".env"); let secrets = workspace.join("secrets.yml"); let escaped = workspace.join("config?.env"); let allowed = workspace.join("notes.txt"); for path in [&dotenv, &secrets, &escaped] { fs::write(path, "protected contents").expect("write protected fixture"); } fs::write(&allowed, "allowed contents").expect("write allowed fixture"); let config = format!( r#"default_permissions = "test" [permissions.test] extends = ":read-only" [permissions.test.filesystem] "{workspace}" = "write" "{workspace}/{{.env,secrets.yml}}*" = "deny" '{workspace}/config\?.env' = "deny" "#, workspace = workspace.display() ); fs::write(temp_dir.path().join("config.toml"), config).expect("write permissions config"); let profile = core_test_support::load_default_config_for_test(&temp_dir) .await .permissions .effective_permission_profile(); for denied_path in [&dotenv, &secrets, &escaped] { assert_denied_read(denied_path, &workspace, &profile).await; } let output = run_path_command("/bin/cat", &allowed, &workspace, &profile).await; assert!(output.status.success(), "allowed read failed: {output:?}"); assert_eq!(output.stdout, b"allowed contents"); } #[tokio::test] async fn seatbelt_blocks_renaming_ancestors_of_explicit_protected_files() { core_test_support::skip_if_sandbox!(); for access in [FileSystemAccessMode::Read, FileSystemAccessMode::Deny] { let (_temp_dir, workspace) = workspace_fixture(); let private = workspace.join("private"); let protected = private.join("secret.env"); let renamed = workspace.join("exposed"); fs::create_dir(&private).expect("create protected directory"); fs::write(&protected, "protected contents").expect("write protected fixture"); let profile = permission_profile(&workspace, vec![(protected.clone().into(), access)]); let command = vec![ "/bin/mv".to_string(), private.display().to_string(), renamed.display().to_string(), ]; let output = run_sandboxed_command(command.clone(), &workspace, &profile).await; assert!( !output.status.success(), "Seatbelt allowed renaming an ancestor of a {access:?} path: {output:?}" ); assert_file_contents(&protected, "protected contents"); assert!(!renamed.as_path().exists(), "rename unexpectedly succeeded"); let unprotected = permission_profile(&workspace, Vec::new()); let control = run_sandboxed_command(command, &workspace, &unprotected).await; assert!( control.status.success(), "rename without a protected descendant should succeed: {control:?}" ); } } #[tokio::test] async fn seatbelt_blocks_renaming_dynamic_glob_ancestors() { core_test_support::skip_if_sandbox!(); for component in [".githu?", "{.github,.gitlab}", r".githu\b", "*"] { let (_temp_dir, workspace) = workspace_fixture(); let github = workspace.join(".github"); let workflows = github.join("workflows"); let protected = workflows.join("release.yml"); let renamed = workspace.join("exposed"); fs::create_dir_all(&workflows).expect("create protected directory"); fs::write(&protected, "protected workflow").expect("write protected fixture"); let profile = denied_glob_profile( &workspace, format!("{}/{component}/workflows/*.yml", workspace.display()), ); let command = vec![ "/bin/mv".to_string(), github.display().to_string(), renamed.display().to_string(), ]; let output = run_sandboxed_command(command.clone(), &workspace, &profile).await; assert!( !output.status.success(), "Seatbelt allowed renaming ancestor matched by {component:?}: {output:?}" ); assert_file_contents(&protected, "protected workflow"); let unprotected = permission_profile(&workspace, Vec::new()); let control = run_sandboxed_command(command, &workspace, &unprotected).await; assert!( control.status.success(), "rename without the deny glob should succeed: {control:?}" ); } } #[tokio::test] async fn seatbelt_blocks_renaming_protected_workspace_into_another_writable_root() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let destination = workspace .parent() .expect("workspace has a parent") .join("destination"); let moved_workspace = destination.join("moved"); let protected = workspace.join("secret.env"); fs::create_dir(&destination).expect("create writable destination"); fs::write(&protected, "protected contents").expect("write protected fixture"); let profile = permission_profile( &workspace, vec![ (destination.clone().into(), FileSystemAccessMode::Write), (protected.clone().into(), FileSystemAccessMode::Read), ], ); let output = run_sandboxed_command( vec![ "/bin/mv".to_string(), workspace.display().to_string(), moved_workspace.display().to_string(), ], &workspace, &profile, ) .await; assert!( !output.status.success(), "Seatbelt allowed moving the workspace into another writable root: {output:?}" ); assert_file_contents(&protected, "protected contents"); assert!( !moved_workspace.as_path().exists(), "workspace unexpectedly moved" ); } #[tokio::test] async fn seatbelt_ancestor_protection_does_not_block_unlinking_regular_files() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let regular_file = workspace.join("regular-file"); let hypothetical_descendant = regular_file.join("secret.env"); fs::write(®ular_file, "replaceable").expect("write regular file"); let profile = permission_profile( &workspace, vec![(hypothetical_descendant.into(), FileSystemAccessMode::Read)], ); let output = run_path_command("/bin/rm", ®ular_file, &workspace, &profile).await; assert!( output.status.success(), "directory-only ancestor protection blocked a regular file: {output:?}" ); assert!( !regular_file.as_path().exists(), "regular file was not removed" ); } #[tokio::test] async fn seatbelt_deny_globs_block_writes_to_existing_and_new_files() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let protected = workspace.join("secret.env"); let allowed = workspace.join("notes.txt"); fs::write(&protected, "original").expect("write protected fixture"); let profile = denied_glob_profile(&workspace, format!("{}/*.env", workspace.display())); for (operation, script) in [ ("overwrite", "printf changed >> \"$1\""), ("truncate", ": > \"$1\""), ] { let output = run_shell_command(script, &protected, &workspace, &profile).await; assert!( !output.status.success(), "Seatbelt allowed {operation} on a denied file: {output:?}" ); assert_file_contents(&protected, "original"); } let unlink = run_path_command("/bin/rm", &protected, &workspace, &profile).await; assert!( !unlink.status.success(), "Seatbelt unexpectedly allowed unlinking a denied glob: {unlink:?}" ); assert_file_contents(&protected, "original"); for (operation, write_script) in [ ("overwrite", "printf changed >> \"$1\""), ("truncate", ": > \"$1\""), ] { let late_protected = workspace.join(format!("late-{operation}.env")); let output = run_after_policy_applied( write_script, &late_protected, Vec::new(), &workspace, &profile, || { fs::write(&late_protected, "created after policy generation") .expect("create protected fixture after sandbox launch"); }, ) .await; assert!( !output.status.success(), "Seatbelt allowed {operation} on a file created after policy generation: {output:?}" ); assert_file_contents(&late_protected, "created after policy generation"); } let allowed_write = run_shell_command("printf allowed > \"$1\"", &allowed, &workspace, &profile).await; assert!( allowed_write.status.success(), "unrelated write should remain allowed: {allowed_write:?}" ); assert_file_contents(&allowed, "allowed"); } #[tokio::test] async fn seatbelt_standard_profiles_preserve_expected_filesystem_access() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let inside = workspace.join("notes.txt"); let outside = workspace.parent().expect("parent").join("outside.txt"); fs::write(&inside, "original").expect("write workspace fixture"); let profile = permission_profile(&workspace, Vec::new()); let script = "set -e; /bin/cat \"$1\" >/dev/null; printf changed >> \"$1\"; /bin/mkdir \"$1.dir\"; /bin/mv \"$1\" \"$1.dir/moved\"; /bin/rm \"$1.dir/moved\"; /bin/rmdir \"$1.dir\""; let inside_output = run_shell_command(script, &inside, &workspace, &profile).await; assert!( inside_output.status.success(), "ordinary workspace operations failed: {inside_output:?}" ); let outside_output = run_shell_command("printf denied > \"$1\"", &outside, &workspace, &profile).await; assert!( !outside_output.status.success(), "write outside the workspace unexpectedly succeeded: {outside_output:?}" ); assert!(!outside.as_path().exists(), "outside file was created"); let readable = workspace.join("readable.txt"); fs::write(&readable, "readable contents").expect("write read-only fixture"); let profile = PermissionProfile::read_only(); let read = run_path_command("/bin/cat", &readable, &workspace, &profile).await; assert!(read.status.success(), "ordinary read failed: {read:?}"); assert_eq!(read.stdout, b"readable contents"); let write = run_shell_command("printf changed > \"$1\"", &readable, &workspace, &profile).await; assert!( !write.status.success(), "read-only write succeeded: {write:?}" ); assert_file_contents(&readable, "readable contents"); } #[tokio::test] async fn seatbelt_blocks_protected_reads_and_writes_through_symlink_aliases() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let private = workspace.join("private"); let alias = workspace.join("alias"); let protected = private.join("secret.env"); let alias_path = alias.join("secret.env"); fs::create_dir(&private).expect("create protected directory"); fs::write(&protected, "protected contents").expect("write protected fixture"); symlink(private.as_path(), alias.as_path()).expect("create directory symlink"); for (restricted_path, accessed_path) in [ (FileSystemPath::from(protected.clone()), alias_path.clone()), ( FileSystemPath::GlobPattern { pattern: format!("{}/*.env", private.display()), }, alias_path.clone(), ), ( FileSystemPath::GlobPattern { pattern: format!("{}/*.env", alias.display()), }, protected.clone(), ), ] { let profile = permission_profile( &workspace, vec![(restricted_path, FileSystemAccessMode::Deny)], ); assert_denied_read(&accessed_path, &workspace, &profile).await; let write = run_shell_command( "printf changed > \"$1\"", &accessed_path, &workspace, &profile, ) .await; assert!( !write.status.success(), "Seatbelt allowed writing through protected symlink alias: {write:?}" ); assert_file_contents(&protected, "protected contents"); } } #[tokio::test] async fn seatbelt_globstar_protects_files_and_ancestors_at_every_depth() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let first = workspace.join("first"); let second = first.join("second"); fs::create_dir_all(&second).expect("create nested fixture directories"); let protected_paths = [ workspace.join("root.env"), first.join("first.env"), second.join("second.env"), ]; for path in &protected_paths { fs::write(path, "protected contents").expect("write protected fixture"); } let profile = denied_glob_profile(&workspace, format!("{}/**/*.env", workspace.display())); for path in &protected_paths { assert_denied_read(path, &workspace, &profile).await; } for ancestor in [&first, &second] { let destination = ancestor.with_extension("renamed"); let output = run_sandboxed_command( vec![ "/bin/mv".to_string(), ancestor.display().to_string(), destination.display().to_string(), ], &workspace, &profile, ) .await; assert!( !output.status.success(), "globstar allowed renaming protected ancestor {}: {output:?}", ancestor.display() ); } } #[tokio::test] async fn seatbelt_brace_alternatives_with_slashes_protect_each_branch() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let nested = workspace.join("private/nested"); let other = workspace.join("other"); let allowed = workspace.join("notes.txt"); fs::create_dir_all(&nested).expect("create nested protected directory"); fs::create_dir(&other).expect("create alternate protected directory"); let protected_paths = [nested.join("secret.env"), other.join("secret.env")]; for path in &protected_paths { fs::write(path, "protected contents").expect("write protected fixture"); } fs::write(&allowed, "allowed contents").expect("write allowed fixture"); let profile = denied_glob_profile( &workspace, format!("{}/{{private/nested,other}}/*.env", workspace.display()), ); for path in &protected_paths { assert_denied_read(path, &workspace, &profile).await; } for ancestor in [&workspace.join("private"), &nested, &other] { let destination = ancestor.with_extension("renamed"); let output = run_sandboxed_command( vec![ "/bin/mv".to_string(), ancestor.display().to_string(), destination.display().to_string(), ], &workspace, &profile, ) .await; assert!( !output.status.success(), "brace alternative allowed renaming {}: {output:?}", ancestor.display() ); } let output = run_path_command("/bin/cat", &allowed, &workspace, &profile).await; assert!( output.status.success(), "brace alternatives produced an invalid or overly broad policy: {output:?}" ); assert_eq!(output.stdout, b"allowed contents"); } #[tokio::test] async fn seatbelt_protects_directory_trees_created_after_policy_application() { core_test_support::skip_if_sandbox!(); for operation in ["read", "rename"] { let (_temp_dir, workspace) = workspace_fixture(); let late_directory = workspace.join("late/deep"); let protected = late_directory.join("secret.env"); let destination = workspace.join("exposed"); let profile = denied_glob_profile(&workspace, format!("{}/**/*.env", workspace.display())); let operation_script = match operation { "read" => "/bin/cat \"$1\"", "rename" => "/bin/mv \"$4\" \"$5\"", _ => unreachable!("unknown operation"), }; let output = run_after_policy_applied( operation_script, &protected, vec![ workspace.join("late").display().to_string(), destination.display().to_string(), ], &workspace, &profile, || { fs::create_dir_all(&late_directory) .expect("create protected directory after policy"); fs::write(&protected, "protected contents").expect("write protected fixture"); }, ) .await; assert!( !output.status.success(), "Seatbelt allowed {operation} after protected directory appeared: {output:?}" ); assert_file_contents(&protected, "protected contents"); } } #[tokio::test] async fn seatbelt_blocks_atomic_rename_exchange_and_destination_replacement() { core_test_support::skip_if_sandbox!(); for (operation, flags) in [("exchange", libc::RENAME_SWAP), ("replace", 0)] { let (_temp_dir, workspace) = workspace_fixture(); let private = workspace.join("private"); let destination = workspace.join("destination"); let protected = private.join("secret.env"); fs::create_dir(&private).expect("create protected directory"); fs::create_dir(&destination).expect("create replacement directory"); fs::write(&protected, "protected contents").expect("write protected fixture"); let profile = permission_profile( &workspace, vec![(protected.clone().into(), FileSystemAccessMode::Read)], ); let script = "import ctypes, os, sys; libc = ctypes.CDLL(None, use_errno=True); result = libc.renameatx_np(-2, os.fsencode(sys.argv[1]), -2, os.fsencode(sys.argv[2]), int(sys.argv[3])); sys.exit(0 if result == 0 else ctypes.get_errno())"; let command = vec![ "/usr/bin/python3".to_string(), "-c".to_string(), script.to_string(), private.display().to_string(), destination.display().to_string(), flags.to_string(), ]; let output = run_sandboxed_command(command.clone(), &workspace, &profile).await; assert!( !output.status.success(), "Seatbelt allowed atomic {operation} of a protected ancestor: {output:?}" ); assert_file_contents(&protected, "protected contents"); let unprotected = permission_profile(&workspace, Vec::new()); let control = run_sandboxed_command(command, &workspace, &unprotected).await; assert!( control.status.success(), "unprotected atomic {operation} should succeed: {control:?}" ); } } #[tokio::test] async fn seatbelt_blocks_protected_ancestor_moves_across_writable_roots() { core_test_support::skip_if_sandbox!(); for destination_is_nested in [false, true] { for use_glob in [false, true] { let (_temp_dir, workspace) = workspace_fixture(); let private = workspace.join("private"); let protected = private.join("secret.env"); let destination = if destination_is_nested { workspace.join("destination") } else { workspace .parent() .expect("workspace has a parent") .join("destination") }; let moved = destination.join("moved"); fs::create_dir(&private).expect("create protected directory"); fs::create_dir(&destination).expect("create writable destination"); fs::write(&protected, "protected contents").expect("write protected fixture"); let protected_path = if use_glob { FileSystemPath::GlobPattern { pattern: format!("{}/*.env", private.display()), } } else { protected.clone().into() }; let profile = permission_profile( &workspace, vec![ (destination.clone().into(), FileSystemAccessMode::Write), (protected_path, FileSystemAccessMode::Deny), ], ); let output = run_sandboxed_command( vec![ "/bin/mv".to_string(), private.display().to_string(), moved.display().to_string(), ], &workspace, &profile, ) .await; assert!( !output.status.success(), "protected ancestor crossed writable roots: nested={destination_is_nested}, glob={use_glob}, output={output:?}" ); assert_file_contents(&protected, "protected contents"); assert!(!moved.as_path().exists(), "protected ancestor was moved"); } } } #[tokio::test] async fn seatbelt_blocks_creating_files_matched_by_denied_globs() { core_test_support::skip_if_sandbox!(); let (_temp_dir, workspace) = workspace_fixture(); let nested = workspace.join("nested"); fs::create_dir(&nested).expect("create nested directory"); let profile = denied_glob_profile(&workspace, format!("{}/**/*.env", workspace.display())); for path in [workspace.join("created.env"), nested.join("created.env")] { let output = run_shell_command("printf created > \"$1\"", &path, &workspace, &profile).await; assert!( !output.status.success(), "Seatbelt allowed creation at a denied path: {output:?}" ); assert!(!path.as_path().exists(), "denied file was created"); } let allowed = nested.join("created.txt"); let output = run_path_command("/usr/bin/touch", &allowed, &workspace, &profile).await; assert!( output.status.success(), "unrelated file creation should succeed: {output:?}" ); assert!(allowed.as_path().exists(), "unrelated file was not created"); }