From 1915f31c03f553bc399ffe05ce9b9d7aaa5c7aee Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Thu, 9 Jul 2026 13:05:15 -0700 Subject: [PATCH] exec-server: materialize filesystem workspace roots --- codex-rs/exec-server/src/fs_sandbox.rs | 33 +++++++----- .../exec-server/tests/file_system_unix.rs | 53 +++++++++++++++++++ 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/codex-rs/exec-server/src/fs_sandbox.rs b/codex-rs/exec-server/src/fs_sandbox.rs index 812d76751ce1..9dfbeba976ff 100644 --- a/codex-rs/exec-server/src/fs_sandbox.rs +++ b/codex-rs/exec-server/src/fs_sandbox.rs @@ -67,10 +67,22 @@ impl FileSystemSandboxRunner { request: FsHelperRequest, ) -> Result { let cwd = sandbox_cwd(sandbox)?; + let native_workspace_roots = sandbox + .workspace_roots + .iter() + .map(native_workspace_root) + .collect::, _>>()?; + let workspace_roots = if native_workspace_roots.is_empty() { + std::slice::from_ref(&cwd.native) + } else { + native_workspace_roots.as_slice() + }; let native_permissions: PermissionProfile = sandbox.permissions.clone().try_into().map_err(|err| { invalid_request(format!("invalid sandbox permission path URI: {err}")) })?; + let native_permissions = + native_permissions.materialize_project_roots_with_workspace_roots(workspace_roots); let mut file_system_policy = native_permissions.file_system_sandbox_policy(); let helper_read_roots = if sandbox.use_legacy_landlock { Vec::new() @@ -89,7 +101,8 @@ impl FileSystemSandboxRunner { &file_system_policy, network_policy, ); - let command = self.sandbox_exec_request(&permission_profile, &cwd, sandbox)?; + let command = + self.sandbox_exec_request(&permission_profile, &cwd, workspace_roots, sandbox)?; let request_json = serde_json::to_vec(&request).map_err(json_error)?; run_command(command, request_json).await } @@ -98,6 +111,7 @@ impl FileSystemSandboxRunner { &self, permission_profile: &PermissionProfile, cwd: &SandboxCwd, + workspace_roots: &[AbsolutePathBuf], sandbox_context: &FileSystemSandboxContext, ) -> Result { let helper = &self.runtime_paths.codex_self_exe; @@ -118,16 +132,6 @@ impl FileSystemSandboxRunner { managed_network: None, additional_permissions: None, }; - let native_workspace_roots = sandbox_context - .workspace_roots - .iter() - .map(native_workspace_root) - .collect::, _>>()?; - let workspace_roots = if native_workspace_roots.is_empty() { - std::slice::from_ref(&cwd.native) - } else { - native_workspace_roots.as_slice() - }; sandbox_manager .transform_for_direct_spawn(SandboxDirectSpawnTransformRequest { workspace_roots, @@ -560,7 +564,12 @@ mod tests { }; let request = runner - .sandbox_exec_request(&permission_profile, &sandbox_cwd, &sandbox_context) + .sandbox_exec_request( + &permission_profile, + &sandbox_cwd, + std::slice::from_ref(&sandbox_cwd.native), + &sandbox_context, + ) .expect("sandbox exec request"); assert_eq!(request.env.get(&path_key), Some(&path)); diff --git a/codex-rs/exec-server/tests/file_system_unix.rs b/codex-rs/exec-server/tests/file_system_unix.rs index c3fc6807877f..bfcf9df601f8 100644 --- a/codex-rs/exec-server/tests/file_system_unix.rs +++ b/codex-rs/exec-server/tests/file_system_unix.rs @@ -23,11 +23,19 @@ use codex_exec_server::CreateDirectoryOptions; #[cfg(target_os = "linux")] use codex_exec_server::Environment; use codex_exec_server::FileMetadata; +use codex_exec_server::FileSystemSandboxContext; 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_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; use tempfile::TempDir; @@ -210,6 +218,51 @@ async fn sandboxed_file_system_helper_finds_bwrap_on_preserved_path() -> Result< Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn remote_read_file_materializes_environment_workspace_roots() -> Result<()> { + let context = create_file_system_context(FileSystemImplementation::Remote).await?; + let file_system = context.file_system; + let tmp = TempDir::new()?; + let workspace = tmp.path().join("workspace"); + let workspace_file = workspace.join("included.txt"); + let excluded_file = tmp.path().join("excluded.txt"); + std::fs::create_dir(&workspace)?; + std::fs::write(&workspace_file, b"included")?; + std::fs::write(&excluded_file, b"excluded")?; + + let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::project_roots(/*subpath*/ None), + }, + access: FileSystemAccessMode::Read, + }]); + let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd( + PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted), + PathUri::from_host_native_path(tmp.path())?, + ); + sandbox.workspace_roots = vec![PathUri::from_host_native_path(&workspace)?]; + + assert_eq!( + file_system + .read_file( + &PathUri::from_host_native_path(&workspace_file)?, + Some(&sandbox), + ) + .await?, + b"included" + ); + let error = file_system + .read_file( + &PathUri::from_host_native_path(&excluded_file)?, + Some(&sandbox), + ) + .await + .expect_err("read outside environment workspace roots should fail"); + assert_sandbox_denied(&error); + + Ok(()) +} + #[test_case(FileSystemImplementation::Local ; "local")] #[test_case(FileSystemImplementation::Remote ; "remote")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]