Download codex-rs/exec-server/tests/file_system/shared.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 39.8 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/tests/file_system/shared.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/exec-server/tests/file_system/shared.rs
-
curl -L -o shared.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/tests/file_system/shared.rs
39.8 kB
| use anyhow::Context; | |
| use anyhow::Result; | |
| use codex_exec_server::CopyOptions; | |
| use codex_exec_server::CreateDirectoryOptions; | |
| use codex_exec_server::ExecServerRuntimePaths; | |
| use codex_exec_server::ExecutorFileSystem; | |
| use codex_exec_server::FILE_READ_CHUNK_SIZE; | |
| use codex_exec_server::FileMetadata; | |
| use codex_exec_server::LocalFileSystem; | |
| use codex_exec_server::ReadDirectoryEntry; | |
| use codex_exec_server::RemoveOptions; | |
| use codex_exec_server::WalkEntry; | |
| use codex_exec_server::WalkEntryKind; | |
| use codex_exec_server::WalkOptions; | |
| use codex_exec_server::WalkOutcome; | |
| use codex_exec_server::WriteFileOptions; | |
| use codex_file_system::MAX_WALK_DEPTH; | |
| use codex_file_system::MAX_WALK_DIRECTORIES; | |
| use codex_file_system::MAX_WALK_ENTRIES; | |
| use codex_protocol::models::AdditionalPermissionProfile; | |
| use codex_protocol::models::FileSystemPermissions; | |
| use codex_protocol::models::PermissionProfile; | |
| use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy; | |
| use codex_sandboxing::policy_transforms::effective_network_sandbox_policy; | |
| use codex_utils_path_uri::PathUri; | |
| use futures::TryStreamExt; | |
| use pretty_assertions::assert_eq; | |
| use std::path::Path; | |
| use tempfile::TempDir; | |
| use test_case::test_case; | |
| use super::support::FileSystemImplementation; | |
| use super::support::absolute_path; | |
| use super::support::create_file_system_context; | |
| use super::support::is_unsupported_restricted_token_host; | |
| use super::support::read_only_sandbox; | |
| use super::support::workspace_write_sandbox; | |
| fn sandbox_context_from_profile_preserves_workspace_write_read_only_subpaths() -> Result<()> { | |
| let tmp = TempDir::new()?; | |
| let writable_dir = tmp.path().join("writable"); | |
| let git_dir = writable_dir.join(".git"); | |
| std::fs::create_dir_all(&git_dir)?; | |
| let sandbox = workspace_write_sandbox(writable_dir.clone()); | |
| let permissions: PermissionProfile = sandbox.permissions.try_into()?; | |
| let policy = permissions.file_system_sandbox_policy(); | |
| let cwd = absolute_path(writable_dir.clone()); | |
| let writable_roots = policy.get_writable_roots_with_cwd(cwd.as_path()); | |
| let writable_dir = absolute_path(std::fs::canonicalize(writable_dir)?); | |
| let git_dir = absolute_path(std::fs::canonicalize(git_dir)?); | |
| let Some(writable_root) = writable_roots | |
| .iter() | |
| .find(|writable_root| writable_root.root == writable_dir) | |
| else { | |
| panic!("writable root should be preserved"); | |
| }; | |
| assert!(writable_root.read_only_subpaths.contains(&git_dir)); | |
| Ok(()) | |
| } | |
| async fn file_system_get_metadata_reports_files_and_directories( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let file_path = tmp.path().join("note.txt"); | |
| let directory_path = tmp.path().join("notes"); | |
| std::fs::write(&file_path, "hello")?; | |
| std::fs::create_dir(&directory_path)?; | |
| let file_metadata = file_system | |
| .get_metadata( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| Default::default(), | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| file_metadata, | |
| FileMetadata { | |
| is_directory: false, | |
| is_file: true, | |
| is_symlink: false, | |
| size: 5, | |
| created_at_ms: file_metadata.created_at_ms, | |
| modified_at_ms: file_metadata.modified_at_ms, | |
| } | |
| ); | |
| assert!(file_metadata.modified_at_ms > 0); | |
| let directory_metadata = file_system | |
| .get_metadata( | |
| &PathUri::from_host_native_path(&directory_path)?, | |
| Default::default(), | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| directory_metadata, | |
| FileMetadata { | |
| is_directory: true, | |
| is_file: false, | |
| is_symlink: false, | |
| size: std::fs::metadata(&directory_path)?.len(), | |
| created_at_ms: directory_metadata.created_at_ms, | |
| modified_at_ms: directory_metadata.modified_at_ms, | |
| } | |
| ); | |
| assert!(directory_metadata.modified_at_ms > 0); | |
| Ok(()) | |
| } | |
| async fn file_system_create_directory_creates_nested_directories( | |
| implementation: FileSystemImplementation, | |
| follow_symlinks: bool, | |
| sandboxed: bool, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let root = tmp.path().canonicalize()?; | |
| let nested_dir = root.join("source").join("nested"); | |
| let sandbox = sandboxed.then(|| workspace_write_sandbox(root)); | |
| let result = file_system | |
| .create_directory( | |
| &PathUri::from_host_native_path(&nested_dir)?, | |
| CreateDirectoryOptions { | |
| recursive: true, | |
| follow_symlinks, | |
| }, | |
| sandbox.as_ref(), | |
| ) | |
| .await; | |
| if is_unsupported_restricted_token_host(&result) { | |
| return Ok(()); | |
| } | |
| result.with_context(|| format!("mode={implementation}, sandboxed={sandboxed}"))?; | |
| assert!(nested_dir.is_dir()); | |
| Ok(()) | |
| } | |
| async fn file_system_write_file_writes_bytes( | |
| implementation: FileSystemImplementation, | |
| follow_symlinks: bool, | |
| sandboxed: bool, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let root = tmp.path().canonicalize()?; | |
| let file_path = root.join("note.txt"); | |
| let sandbox = sandboxed.then(|| workspace_write_sandbox(root.clone())); | |
| let result = file_system | |
| .write_file( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| b"hello from trait".to_vec(), | |
| WriteFileOptions { follow_symlinks }, | |
| sandbox.as_ref(), | |
| ) | |
| .await; | |
| if is_unsupported_restricted_token_host(&result) { | |
| return Ok(()); | |
| } | |
| result.with_context(|| format!("mode={implementation}, sandboxed={sandboxed}"))?; | |
| assert_eq!(std::fs::read(file_path)?, b"hello from trait"); | |
| let file_path = root.join("existing.txt"); | |
| std::fs::write(&file_path, b"before")?; | |
| file_system | |
| .write_file( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| b"after".to_vec(), | |
| WriteFileOptions { follow_symlinks }, | |
| sandbox.as_ref(), | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}, sandboxed={sandboxed}"))?; | |
| assert_eq!(std::fs::read(file_path)?, b"after"); | |
| Ok(()) | |
| } | |
| fn path_uri_join_and_parent_preserve_lexical_paths() -> Result<()> { | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let source_dir_uri = PathUri::from_host_native_path(&source_dir)?; | |
| let joined_nested = source_dir_uri.join("nested/note.txt")?; | |
| assert_eq!( | |
| joined_nested, | |
| PathUri::from_host_native_path(source_dir.join("nested").join("note.txt"))? | |
| ); | |
| let joined_parent = joined_nested.parent(); | |
| assert_eq!( | |
| joined_parent, | |
| Some(PathUri::from_host_native_path(source_dir.join("nested"))?) | |
| ); | |
| let joined_parent_traversal = source_dir_uri.join("../outside")?; | |
| assert_eq!( | |
| joined_parent_traversal, | |
| PathUri::from_host_native_path(source_dir.join("../outside"))? | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_read_file_returns_bytes( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let file_path = tmp.path().join("note.txt"); | |
| std::fs::write(&file_path, "hello from trait")?; | |
| let contents = file_system | |
| .read_file( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| Default::default(), | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(contents, b"hello from trait"); | |
| Ok(()) | |
| } | |
| async fn file_system_read_file_stream_returns_bounded_chunks( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let file_path = tmp.path().join("blocks.bin"); | |
| let contents = (0..FILE_READ_CHUNK_SIZE * 2 + 17) | |
| .map(|index| (index % 251) as u8) | |
| .collect::<Vec<_>>(); | |
| std::fs::write(&file_path, &contents)?; | |
| let path = PathUri::from_host_native_path(file_path)?; | |
| let sandbox = read_only_sandbox(tmp.path().to_path_buf()); | |
| for sandbox in [None, Some(&sandbox)] { | |
| let chunks = file_system | |
| .read_file_stream(&path, sandbox) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))? | |
| .try_collect::<Vec<_>>() | |
| .await?; | |
| assert!( | |
| chunks | |
| .iter() | |
| .all(|chunk| !chunk.is_empty() && chunk.len() <= FILE_READ_CHUNK_SIZE) | |
| ); | |
| assert_eq!( | |
| chunks | |
| .iter() | |
| .flat_map(|chunk| chunk.iter().copied()) | |
| .collect::<Vec<_>>(), | |
| contents | |
| ); | |
| } | |
| Ok(()) | |
| } | |
| async fn file_system_read_file_text_returns_string( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let file_path = tmp.path().join("note.txt"); | |
| std::fs::write(&file_path, "hello from trait")?; | |
| let contents = file_system | |
| .read_file_text( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| Default::default(), | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(contents, "hello from trait"); | |
| Ok(()) | |
| } | |
| async fn file_system_copy_copies_file(implementation: FileSystemImplementation) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_file = tmp.path().join("source.txt"); | |
| let copied_file = tmp.path().join("copy.txt"); | |
| std::fs::write(&source_file, "hello from trait")?; | |
| file_system | |
| .copy( | |
| &PathUri::from_host_native_path(&source_file)?, | |
| &PathUri::from_host_native_path(&copied_file)?, | |
| CopyOptions { recursive: false }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(std::fs::read_to_string(copied_file)?, "hello from trait"); | |
| Ok(()) | |
| } | |
| async fn file_system_copy_copies_directory_recursively( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let nested_dir = source_dir.join("nested"); | |
| let nested_file = nested_dir.join("note.txt"); | |
| let copied_dir = tmp.path().join("copied"); | |
| std::fs::create_dir_all(&nested_dir)?; | |
| std::fs::write(&nested_file, "hello from trait")?; | |
| file_system | |
| .copy( | |
| &PathUri::from_host_native_path(&source_dir)?, | |
| &PathUri::from_host_native_path(&copied_dir)?, | |
| CopyOptions { recursive: true }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| std::fs::read_to_string(copied_dir.join("nested").join("note.txt"))?, | |
| "hello from trait" | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_read_directory_lists_entries( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| std::fs::create_dir_all(source_dir.join("nested"))?; | |
| std::fs::write(source_dir.join("root.txt"), "hello")?; | |
| let mut entries = file_system | |
| .read_directory( | |
| &PathUri::from_host_native_path(&source_dir)?, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| entries.sort_by(|left, right| left.file_name.cmp(&right.file_name)); | |
| assert_eq!( | |
| entries, | |
| vec![ | |
| ReadDirectoryEntry { | |
| file_name: "nested".to_string(), | |
| is_directory: true, | |
| is_file: false, | |
| }, | |
| ReadDirectoryEntry { | |
| file_name: "root.txt".to_string(), | |
| is_directory: false, | |
| is_file: true, | |
| }, | |
| ] | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_walk_returns_a_bounded_tree( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let nested_dir = source_dir.join("nested"); | |
| std::fs::create_dir_all(&nested_dir)?; | |
| std::fs::write(source_dir.join("root.txt"), "root")?; | |
| std::fs::write(nested_dir.join("note.txt"), "nested")?; | |
| let source_uri = PathUri::from_host_native_path(&source_dir)?; | |
| let outcome = file_system | |
| .walk( | |
| &source_uri, | |
| WalkOptions { | |
| max_depth: 4, | |
| max_directories: 10, | |
| max_entries: 10, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| outcome, | |
| WalkOutcome { | |
| entries: vec![ | |
| WalkEntry { | |
| path: PathUri::from_host_native_path(&nested_dir)?, | |
| kind: WalkEntryKind::Directory, | |
| }, | |
| WalkEntry { | |
| path: PathUri::from_host_native_path(source_dir.join("root.txt"))?, | |
| kind: WalkEntryKind::File, | |
| }, | |
| WalkEntry { | |
| path: PathUri::from_host_native_path(nested_dir.join("note.txt"))?, | |
| kind: WalkEntryKind::File, | |
| }, | |
| ], | |
| errors: Vec::new(), | |
| truncated: false, | |
| } | |
| ); | |
| let root_entries = vec![ | |
| WalkEntry { | |
| path: PathUri::from_host_native_path(&nested_dir)?, | |
| kind: WalkEntryKind::Directory, | |
| }, | |
| WalkEntry { | |
| path: PathUri::from_host_native_path(source_dir.join("root.txt"))?, | |
| kind: WalkEntryKind::File, | |
| }, | |
| ]; | |
| let shallow = file_system | |
| .walk( | |
| &source_uri, | |
| WalkOptions { | |
| max_depth: 0, | |
| max_directories: 10, | |
| max_entries: 10, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| shallow, | |
| WalkOutcome { | |
| entries: root_entries.clone(), | |
| errors: Vec::new(), | |
| truncated: false, | |
| } | |
| ); | |
| let directory_bounded = file_system | |
| .walk( | |
| &source_uri, | |
| WalkOptions { | |
| max_depth: 4, | |
| max_directories: 1, | |
| max_entries: 10, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| directory_bounded, | |
| WalkOutcome { | |
| entries: root_entries, | |
| errors: Vec::new(), | |
| truncated: true, | |
| } | |
| ); | |
| let bounded = file_system | |
| .walk( | |
| &source_uri, | |
| WalkOptions { | |
| max_depth: 4, | |
| max_directories: 10, | |
| max_entries: 1, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| bounded, | |
| WalkOutcome { | |
| entries: vec![WalkEntry { | |
| path: PathUri::from_host_native_path(&nested_dir)?, | |
| kind: WalkEntryKind::Directory, | |
| }], | |
| errors: Vec::new(), | |
| truncated: true, | |
| } | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_walk_handles_invalid_roots_and_limits( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let file_path = tmp.path().join("file.txt"); | |
| std::fs::write(&file_path, "contents")?; | |
| let missing = PathUri::from_host_native_path(tmp.path().join("missing"))?; | |
| let options = WalkOptions { | |
| max_depth: 8, | |
| max_directories: 100, | |
| max_entries: 100, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }; | |
| let outcome = file_system | |
| .walk( | |
| &PathUri::from_host_native_path(file_path)?, | |
| options, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(outcome, WalkOutcome::default()); | |
| let error = file_system | |
| .walk(&missing, options, /*sandbox*/ None) | |
| .await | |
| .expect_err("a missing root must fail"); | |
| assert_eq!(error.kind(), std::io::ErrorKind::NotFound); | |
| let zero_limit_message = "filesystem walk limits must be greater than zero"; | |
| let excessive_limit_message = format!( | |
| "filesystem walk limits exceed maximums: depth={MAX_WALK_DEPTH}, directories={MAX_WALK_DIRECTORIES}, entries={MAX_WALK_ENTRIES}" | |
| ); | |
| for (max_depth, max_directories, max_entries, message) in [ | |
| (8, 0, 100, zero_limit_message), | |
| (8, 100, 0, zero_limit_message), | |
| ( | |
| MAX_WALK_DEPTH + 1, | |
| 100, | |
| 100, | |
| excessive_limit_message.as_str(), | |
| ), | |
| ( | |
| 8, | |
| MAX_WALK_DIRECTORIES + 1, | |
| 100, | |
| excessive_limit_message.as_str(), | |
| ), | |
| ( | |
| 8, | |
| 100, | |
| MAX_WALK_ENTRIES + 1, | |
| excessive_limit_message.as_str(), | |
| ), | |
| ] { | |
| let options = WalkOptions { | |
| max_depth, | |
| max_directories, | |
| max_entries, | |
| ..options | |
| }; | |
| // Invalid limits take precedence over the missing root. | |
| let error = file_system | |
| .walk(&missing, options, /*sandbox*/ None) | |
| .await | |
| .expect_err("invalid walk limits must fail"); | |
| assert_eq!( | |
| (error.kind(), error.to_string()), | |
| (std::io::ErrorKind::InvalidInput, message.to_owned()), | |
| "mode={implementation}, options={options:?}", | |
| ); | |
| } | |
| Ok(()) | |
| } | |
| async fn file_system_walk_honors_read_sandbox( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let file_path = source_dir.join("note.txt"); | |
| std::fs::create_dir_all(&source_dir)?; | |
| std::fs::write(&file_path, "sandboxed")?; | |
| let sandbox = read_only_sandbox(source_dir.clone()); | |
| let outcome = file_system | |
| .walk( | |
| &PathUri::from_host_native_path(&source_dir)?, | |
| WalkOptions { | |
| max_depth: 1, | |
| max_directories: 2, | |
| max_entries: 2, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }, | |
| Some(&sandbox), | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| outcome, | |
| WalkOutcome { | |
| entries: vec![WalkEntry { | |
| path: PathUri::from_host_native_path(file_path)?, | |
| kind: WalkEntryKind::File, | |
| }], | |
| errors: Vec::new(), | |
| truncated: false, | |
| } | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_remove_removes_directory( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let directory_path = tmp.path().join("remove-me"); | |
| std::fs::create_dir_all(directory_path.join("nested"))?; | |
| file_system | |
| .remove( | |
| &PathUri::from_host_native_path(&directory_path)?, | |
| RemoveOptions { | |
| recursive: true, | |
| force: true, | |
| follow_symlinks: true, | |
| }, | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert!(!directory_path.exists()); | |
| Ok(()) | |
| } | |
| async fn file_system_remove_no_follow_removes_file_and_empty_directory( | |
| implementation: FileSystemImplementation, | |
| sandboxed: bool, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let root = tmp.path().canonicalize()?; | |
| let sandbox = sandboxed.then(|| workspace_write_sandbox(root.clone())); | |
| let options = RemoveOptions { | |
| recursive: false, | |
| force: false, | |
| follow_symlinks: false, | |
| }; | |
| let file_path = root.join("remove-me.txt"); | |
| std::fs::write(&file_path, b"remove")?; | |
| let result = file_system | |
| .remove( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| options, | |
| sandbox.as_ref(), | |
| ) | |
| .await; | |
| if is_unsupported_restricted_token_host(&result) { | |
| return Ok(()); | |
| } | |
| result.with_context(|| format!("mode={implementation}, sandboxed={sandboxed}"))?; | |
| assert!(!file_path.exists()); | |
| let directory_path = root.join("remove-me"); | |
| std::fs::create_dir(&directory_path)?; | |
| file_system | |
| .remove( | |
| &PathUri::from_host_native_path(&directory_path)?, | |
| options, | |
| sandbox.as_ref(), | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}, sandboxed={sandboxed}"))?; | |
| assert!(!directory_path.exists()); | |
| Ok(()) | |
| } | |
| async fn file_system_write_file_reports_missing_parent( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let missing_parent_path = tmp.path().join("missing").join("note.txt"); | |
| let error = match file_system | |
| .write_file( | |
| &PathUri::from_host_native_path(&missing_parent_path)?, | |
| b"hello from trait".to_vec(), | |
| Default::default(), | |
| /*sandbox*/ None, | |
| ) | |
| .await | |
| { | |
| Ok(()) => anyhow::bail!("write should fail when parent directory is absent"), | |
| Err(error) => error, | |
| }; | |
| assert_eq!( | |
| error.kind(), | |
| std::io::ErrorKind::NotFound, | |
| "mode={implementation}" | |
| ); | |
| assert!(!missing_parent_path.exists(), "mode={implementation}"); | |
| Ok(()) | |
| } | |
| async fn file_system_copy_rejects_directory_without_recursive( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| std::fs::create_dir_all(&source_dir)?; | |
| let error = file_system | |
| .copy( | |
| &PathUri::from_host_native_path(&source_dir)?, | |
| &PathUri::from_host_native_path(tmp.path().join("dest"))?, | |
| CopyOptions { recursive: false }, | |
| /*sandbox*/ None, | |
| ) | |
| .await; | |
| let error = error.expect_err("copying a directory without recursion should fail"); | |
| assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); | |
| assert_eq!( | |
| error.to_string(), | |
| "fs/copy requires recursive: true when sourcePath is a directory" | |
| ); | |
| Ok(()) | |
| } | |
| async fn file_system_sandboxed_metadata_and_read_allow_readable_root( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let allowed_dir = tmp.path().join("allowed"); | |
| let file_path = allowed_dir.join("note.txt"); | |
| std::fs::create_dir_all(&allowed_dir)?; | |
| std::fs::write(&file_path, "sandboxed hello")?; | |
| let sandbox = read_only_sandbox(allowed_dir); | |
| let metadata = file_system | |
| .get_metadata( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| Default::default(), | |
| Some(&sandbox), | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!( | |
| metadata, | |
| FileMetadata { | |
| is_directory: false, | |
| is_file: true, | |
| is_symlink: false, | |
| size: 15, | |
| created_at_ms: metadata.created_at_ms, | |
| modified_at_ms: metadata.modified_at_ms, | |
| } | |
| ); | |
| let contents = file_system | |
| .read_file( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| Default::default(), | |
| Some(&sandbox), | |
| ) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(contents, b"sandboxed hello"); | |
| let chunks = file_system | |
| .read_file_stream(&PathUri::from_host_native_path(&file_path)?, Some(&sandbox)) | |
| .await | |
| .with_context(|| format!("stream mode={implementation}"))? | |
| .try_collect::<Vec<_>>() | |
| .await?; | |
| assert_eq!(chunks.concat(), b"sandboxed hello"); | |
| Ok(()) | |
| } | |
| async fn sandboxed_file_operations_cannot_read_helper_siblings() -> Result<()> { | |
| let helper_paths = crate::common::exec_server::test_codex_helper_paths()?; | |
| let root = TempDir::new()?; | |
| let runtime_dir = root.path().join("runtime"); | |
| let workspace = root.path().join("workspace"); | |
| std::fs::create_dir(&runtime_dir)?; | |
| std::fs::create_dir(&workspace)?; | |
| let helper = runtime_dir.join("codex-test-helper"); | |
| std::fs::hard_link(&helper_paths.codex_exe, &helper) | |
| .or_else(|_| std::fs::copy(&helper_paths.codex_exe, &helper).map(|_| ()))?; | |
| let linux_sandbox = if helper_paths.codex_linux_sandbox_exe.is_some() { | |
| let alias = runtime_dir.join("codex-linux-sandbox"); | |
| std::fs::hard_link(&helper, &alias) | |
| .or_else(|_| std::fs::copy(&helper, &alias).map(|_| ()))?; | |
| Some(alias) | |
| } else { | |
| None | |
| }; | |
| let file_system = | |
| LocalFileSystem::with_runtime_paths(ExecServerRuntimePaths::new(helper, linux_sandbox)?); | |
| let sibling = runtime_dir.join("credentials.json"); | |
| std::fs::write(&sibling, "secret")?; | |
| let escaping_link = workspace.join("credentials-link"); | |
| std::os::unix::fs::symlink(&sibling, &escaping_link)?; | |
| let sandbox = workspace_write_sandbox(workspace.clone()); | |
| let allowed_file = workspace.join("allowed.txt"); | |
| std::fs::write(&allowed_file, b"allowed")?; | |
| let allowed_contents = file_system | |
| .read_file( | |
| &PathUri::from_host_native_path(&allowed_file)?, | |
| Default::default(), | |
| Some(&sandbox), | |
| ) | |
| .await?; | |
| assert_eq!(allowed_contents, b"allowed"); | |
| assert!( | |
| file_system | |
| .read_directory( | |
| &PathUri::from_host_native_path("/Applications")?, | |
| Some(&sandbox) | |
| ) | |
| .await | |
| .is_err(), | |
| "filesystem helpers should not inherit the normal process sandbox's /Applications access" | |
| ); | |
| let sibling_uri = PathUri::from_host_native_path(&sibling)?; | |
| let destination = PathUri::from_host_native_path(workspace.join("copied.json"))?; | |
| for path in [sibling, escaping_link] { | |
| let path = PathUri::from_host_native_path(path)?; | |
| assert!( | |
| file_system | |
| .read_file(&path, Default::default(), Some(&sandbox)) | |
| .await | |
| .is_err(), | |
| "sandboxed read unexpectedly accessed helper sibling {path}" | |
| ); | |
| assert!( | |
| file_system | |
| .read_file_stream(&path, Some(&sandbox)) | |
| .await | |
| .is_err(), | |
| "sandboxed streaming unexpectedly accessed helper sibling {path}" | |
| ); | |
| } | |
| assert!( | |
| file_system | |
| .copy( | |
| &sibling_uri, | |
| &destination, | |
| CopyOptions { recursive: false }, | |
| Some(&sandbox), | |
| ) | |
| .await | |
| .is_err(), | |
| "sandboxed copy unexpectedly accessed helper sibling {sibling_uri}" | |
| ); | |
| Ok(()) | |
| } | |
| pub(crate) async fn assert_canonicalize_resolves_directory_alias( | |
| implementation: FileSystemImplementation, | |
| create_directory_alias: impl FnOnce(&Path, &Path) -> Result<()>, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let nested_dir = source_dir.join("nested"); | |
| let file_path = nested_dir.join("note.txt"); | |
| let alias_dir = tmp.path().join("source-alias"); | |
| std::fs::create_dir_all(&nested_dir)?; | |
| std::fs::write(&file_path, "canonical hello")?; | |
| create_directory_alias(&source_dir, &alias_dir)?; | |
| let requested_path = PathUri::from_host_native_path(alias_dir.join("nested").join("note.txt"))?; | |
| let expected_path = PathUri::from_host_native_path(std::fs::canonicalize(&file_path)?)?; | |
| assert_ne!(requested_path, expected_path); | |
| let canonical_path = file_system | |
| .canonicalize(&requested_path, /*sandbox*/ None) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(canonical_path, expected_path); | |
| Ok(()) | |
| } | |
| pub(crate) async fn assert_sandboxed_canonicalize_resolves_directory_alias( | |
| implementation: FileSystemImplementation, | |
| create_directory_alias: impl FnOnce(&Path, &Path) -> Result<()>, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| let nested_dir = source_dir.join("nested"); | |
| let file_path = nested_dir.join("note.txt"); | |
| let alias_dir = tmp.path().join("source-alias"); | |
| std::fs::create_dir_all(&nested_dir)?; | |
| std::fs::write(&file_path, "sandboxed canonical hello")?; | |
| create_directory_alias(&source_dir, &alias_dir)?; | |
| let sandbox = read_only_sandbox(tmp.path().to_path_buf()); | |
| let requested_path = PathUri::from_host_native_path(alias_dir.join("nested").join("note.txt"))?; | |
| let expected_path = PathUri::from_host_native_path(std::fs::canonicalize(&file_path)?)?; | |
| assert_ne!(requested_path, expected_path); | |
| let canonical_path = file_system | |
| .canonicalize(&requested_path, Some(&sandbox)) | |
| .await | |
| .with_context(|| format!("mode={implementation}"))?; | |
| assert_eq!(canonical_path, expected_path); | |
| Ok(()) | |
| } | |
| /// Verifies that effective additional permissions extend a read-only sandbox with a writable root. | |
| async fn file_system_sandboxed_write_allows_additional_write_root( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let readable_dir = tmp.path().join("readable"); | |
| let writable_dir = tmp.path().join("writable"); | |
| let file_path = writable_dir.join("note.txt"); | |
| std::fs::create_dir_all(&readable_dir)?; | |
| std::fs::create_dir_all(&writable_dir)?; | |
| let mut sandbox = read_only_sandbox(readable_dir); | |
| let additional_permissions = AdditionalPermissionProfile { | |
| network: None, | |
| file_system: Some(FileSystemPermissions::from_read_write_roots( | |
| /*read*/ None, | |
| Some(vec![absolute_path(writable_dir)]), | |
| )), | |
| }; | |
| let native_permissions: PermissionProfile = sandbox.permissions.clone().try_into()?; | |
| let file_system_policy = effective_file_system_sandbox_policy( | |
| &native_permissions.file_system_sandbox_policy(), | |
| Some(&additional_permissions), | |
| ); | |
| let network_policy = effective_network_sandbox_policy( | |
| native_permissions.network_sandbox_policy(), | |
| Some(&additional_permissions), | |
| ); | |
| sandbox.permissions = PermissionProfile::from_runtime_permissions_with_enforcement( | |
| native_permissions.enforcement(), | |
| &file_system_policy, | |
| network_policy, | |
| ) | |
| .into(); | |
| file_system | |
| .write_file( | |
| &PathUri::from_host_native_path(&file_path)?, | |
| b"created".to_vec(), | |
| Default::default(), | |
| Some(&sandbox), | |
| ) | |
| .await | |
| .with_context(|| format!("write file through additional root mode={implementation}"))?; | |
| assert_eq!(std::fs::read(&file_path)?, b"created"); | |
| Ok(()) | |
| } | |
| async fn file_system_copy_rejects_copying_directory_into_descendant( | |
| implementation: FileSystemImplementation, | |
| ) -> Result<()> { | |
| let context = create_file_system_context(implementation).await?; | |
| let file_system = context.file_system; | |
| let tmp = TempDir::new()?; | |
| let source_dir = tmp.path().join("source"); | |
| std::fs::create_dir_all(source_dir.join("nested"))?; | |
| let error = file_system | |
| .copy( | |
| &PathUri::from_host_native_path(&source_dir)?, | |
| &PathUri::from_host_native_path(source_dir.join("nested").join("copy"))?, | |
| CopyOptions { recursive: true }, | |
| /*sandbox*/ None, | |
| ) | |
| .await; | |
| let error = error.expect_err("copying a directory into itself should fail"); | |
| assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); | |
| assert_eq!( | |
| error.to_string(), | |
| "fs/copy cannot copy a directory to itself or one of its descendants" | |
| ); | |
| Ok(()) | |
| } | |