exec-server: require explicit filesystem sandbox cwd (#19046)

## Why

This is a cleanup PR for the `PermissionProfile` migration stack. #19016
fixed remote exec-server sandbox contexts so Docker-backed filesystem
requests use a request/container `cwd` instead of leaking the local test
runner `cwd`. That exposed the broader API problem:
`FileSystemSandboxContext::new(SandboxPolicy)` could still reconstruct
filesystem permissions by reading the exec-server process cwd with
`AbsolutePathBuf::current_dir()`.

That made `cwd`-dependent legacy entries, such as `:cwd`,
`:project_roots`, and relative deny globs, depend on ambient process
state instead of the request sandbox `cwd`. As later PRs make
`PermissionProfile` the primary permissions abstraction, sandbox
contexts should be explicit about whether they carry a request `cwd` or
are profile-only. Removing the implicit constructor prevents new call
sites from accidentally rebuilding permissions against the wrong `cwd`.

## What changed

- Removed `FileSystemSandboxContext::new(SandboxPolicy)`.
- Kept production callers on explicit constructors:
`from_legacy_sandbox_policy(..., cwd)`, `from_permission_profile(...)`,
and `from_permission_profile_with_cwd(...)`.
- Updated exec-server test helpers to construct `PermissionProfile`
values directly instead of routing through legacy `SandboxPolicy`
projections.
- Updated the environment regression test to use an explicit restricted
profile with no synthetic `cwd`.

## Verification

- `cargo test -p codex-exec-server`
- `just fix -p codex-exec-server`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19046).
* #18288
* #18287
* #18286
* #18285
* #18284
* #18283
* #18282
* #18281
* #18280
* __->__ #19046
This commit is contained in:
Michael Bolin
2026-04-22 16:05:12 -07:00
committed by GitHub
parent 46142c3cb0
commit 44dbd9e48a
5 changed files with 100 additions and 100 deletions

View File

@@ -22,9 +22,11 @@ use codex_exec_server::LocalFileSystem;
use codex_exec_server::ReadDirectoryEntry;
use codex_exec_server::RemoveOptions;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -80,38 +82,47 @@ fn absolute_path(path: std::path::PathBuf) -> AbsolutePathBuf {
}
fn read_only_sandbox(readable_root: std::path::PathBuf) -> FileSystemSandboxContext {
FileSystemSandboxContext::new(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![absolute_path(readable_root)],
let readable_root = absolute_path(readable_root);
sandbox_context(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: readable_root,
},
network_access: false,
})
access: FileSystemAccessMode::Read,
}])
}
fn workspace_write_sandbox(writable_root: std::path::PathBuf) -> FileSystemSandboxContext {
FileSystemSandboxContext::new(SandboxPolicy::WorkspaceWrite {
writable_roots: vec![absolute_path(writable_root)],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![],
let writable_root = absolute_path(writable_root);
sandbox_context(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: writable_root,
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
access: FileSystemAccessMode::Write,
}])
}
fn sandbox_context(entries: Vec<FileSystemSandboxEntry>) -> FileSystemSandboxContext {
FileSystemSandboxContext::from_permission_profile(PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(false),
}),
file_system: Some(FileSystemPermissions {
entries,
glob_scan_max_depth: None,
}),
})
}
#[test]
fn sandbox_context_new_preserves_legacy_workspace_write_read_only_subpaths() -> Result<()> {
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 cwd = sandbox.cwd.as_ref().expect("sandbox cwd");
let policy = sandbox.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)?);