Files
codex/codex-rs/exec-server/tests/file_system/support.rs
Adam Perry @ OpenAI 841b5490b2 Preserve filesystem sandbox policy context when the cwd disappears (#46112)
## Why

Removing the selected working directory can prevent filesystem sandbox helpers from launching, even when the requested absolute paths remain accessible. Permission rules must stay anchored to the selected directory while those operations continue.

## What changed

- Require a policy `cwd` in `FileSystemSandboxContext` and launch filesystem helpers from the filesystem root while preserving the policy directory and workspace roots.
- Carry explicit `policyContext` in filesystem RPCs, preserving legacy wire fields and resolving omitted directories from older clients at executor ingress.
- Keep permission paths as executor file URIs and validate host compatibility where they are enforced.
- Bind Windows relative denial globs to the policy directory before changing the helper's launch directory, preserving home-relative patterns.

## Testing

Add regression coverage for `apply_patch` after working-directory removal, legacy RPC directory fallbacks, cross-platform permission URI transport, and Windows relative read denials. The patch regression verifies that an allowed file is updated while an explicitly denied file remains unreadable and unchanged.

GitOrigin-RevId: b0f4db722b27cb72ec129fc297c85732afac11f7
2026-09-17 04:37:54 +00:00

177 lines
6.0 KiB
Rust

use std::fmt;
use std::sync::Arc;
use anyhow::Result;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::LocalFileSystem;
use codex_exec_server::WindowsSandboxSelection;
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_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use crate::common::exec_server::ExecServerHarness;
use crate::common::exec_server::TestCodexHelperPaths;
use crate::common::exec_server::exec_server;
use crate::common::exec_server::test_codex_helper_paths;
pub(crate) struct FileSystemContext {
pub(crate) file_system: Arc<dyn ExecutorFileSystem>,
_helper_paths: Option<TestCodexHelperPaths>,
_server: Option<ExecServerHarness>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum FileSystemImplementation {
Local,
Remote,
}
impl fmt::Display for FileSystemImplementation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local => formatter.write_str("local"),
Self::Remote => formatter.write_str("remote"),
}
}
}
pub(crate) async fn create_file_system_context(
implementation: FileSystemImplementation,
) -> Result<FileSystemContext> {
match implementation {
FileSystemImplementation::Local => {
let helper_paths = test_codex_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(
helper_paths.codex_exe.clone(),
helper_paths.codex_linux_sandbox_exe.clone(),
)?;
Ok(FileSystemContext {
file_system: Arc::new(LocalFileSystem::with_runtime_paths(runtime_paths)),
_helper_paths: Some(helper_paths),
_server: None,
})
}
FileSystemImplementation::Remote => {
let server = exec_server().await?;
let environment =
Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
Ok(FileSystemContext {
file_system: environment.get_filesystem(),
_helper_paths: None,
_server: Some(server),
})
}
}
}
#[cfg(windows)]
pub(crate) fn is_unsupported_restricted_token_host<T>(result: &std::io::Result<T>) -> bool {
result
.as_ref()
.err()
.is_some_and(|err| err.to_string().contains("CreateRestrictedToken failed: 87"))
}
pub(crate) fn absolute_path(path: std::path::PathBuf) -> AbsolutePathBuf {
assert!(
path.is_absolute(),
"path must be absolute: {}",
path.display()
);
AbsolutePathBuf::try_from(path).expect("path should be absolute")
}
pub(crate) fn read_only_sandbox(readable_root: std::path::PathBuf) -> FileSystemSandboxContext {
let readable_root = absolute_path(readable_root);
let cwd = PathUri::from_abs_path(&readable_root);
let entries = vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: readable_root.into(),
},
access: FileSystemAccessMode::Read,
missing_path_behavior: None,
}];
sandbox_context(entries, cwd)
}
#[cfg(not(windows))]
pub(crate) fn workspace_write_sandbox(
writable_root: std::path::PathBuf,
) -> FileSystemSandboxContext {
let writable_root = absolute_path(writable_root);
let cwd = PathUri::from_abs_path(&writable_root);
let entries = vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: writable_root.into(),
},
access: FileSystemAccessMode::Write,
missing_path_behavior: None,
}];
sandbox_context(entries, cwd)
}
#[cfg(windows)]
pub(crate) fn workspace_write_sandbox(
writable_root: std::path::PathBuf,
) -> FileSystemSandboxContext {
let writable_root = absolute_path(writable_root);
// Keep the runtime policy aligned with the legacy workspace-write projection used by the
// unelevated restricted-token preflight.
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
),
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
FileSystemAccessMode::Write,
),
]);
let mut sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
PathUri::from_abs_path(&writable_root),
);
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
sandbox
}
fn sandbox_context(
mut entries: Vec<FileSystemSandboxEntry>,
cwd: PathUri,
) -> FileSystemSandboxContext {
if cfg!(windows) {
// Restricted-token sandboxing cannot enforce read restrictions, so leave the root
// readable while exercising the requested write restrictions.
entries.push(FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
));
}
let mut sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::restricted(entries),
NetworkSandboxPolicy::Restricted,
),
cwd,
);
if cfg!(windows) {
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
}
sandbox
}