sandboxing: migrate cwd inputs to PathUri (#27816)

## Why

Sandbox cwd values can cross app-server and exec-server host boundaries.
They should retain URI semantics until the receiving host validates them
instead of being interpreted early as native paths.

## What

- Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
and transform inputs.
- Convert command and policy cwd once in `SandboxManager::transform`,
then keep launch requests native.
- Preserve sandbox cwd over remote filesystem transport and reject
non-native URIs without fallback.
- Cache paired native/URI turn-environment cwd values during migration,
with immutable access to keep them synchronized.
- Extend existing protocol, forwarding, transform, and core runtime
tests.
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-12 11:38:01 -07:00
1 parent 84520225b9
commit 52a50aec70
40 files changed
+546 -228

No files matched your search

+58 -8
View File
@@ -15,8 +15,10 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
use std::ffi::OsString;
use std::io;
use std::path::Path;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -86,15 +88,20 @@ pub fn with_managed_mitm_ca_readable_root(
pub struct SandboxCommand {
pub program: OsString,
pub args: Vec<String>,
pub cwd: AbsolutePathBuf,
pub cwd: PathUri,
pub env: HashMap<String, String>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
}
/// A host-native launch request produced after [`SandboxManager::transform`] validates URI inputs.
/// Build this only at the execution boundary: in exec-server, or in its logical equivalent within
/// app-server. Orchestration and transport code should retain [`PathUri`] values and defer
/// conversion to native paths until this request is created.
#[derive(Debug)]
pub struct SandboxExecRequest {
pub command: Vec<String>,
pub cwd: AbsolutePathBuf,
pub sandbox_policy_cwd: AbsolutePathBuf,
pub env: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub sandbox: SandboxType,
@@ -117,7 +124,7 @@ pub struct SandboxTransformRequest<'a> {
// TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>>
// to make shared ownership explicit across runtime/sandbox plumbing.
pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a Path,
pub sandbox_policy_cwd: &'a PathUri,
pub codex_linux_sandbox_exe: Option<&'a Path>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
@@ -126,6 +133,14 @@ pub struct SandboxTransformRequest<'a> {
#[derive(Debug)]
pub enum SandboxTransformError {
InvalidCommandCwd {
cwd: PathUri,
source: io::Error,
},
InvalidSandboxPolicyCwd {
cwd: PathUri,
source: io::Error,
},
MissingLinuxSandboxExecutable,
#[cfg(target_os = "linux")]
Wsl1UnsupportedForBubblewrap,
@@ -136,6 +151,16 @@ pub enum SandboxTransformError {
impl std::fmt::Display for SandboxTransformError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidCommandCwd { cwd, source } => {
write!(
f,
"command cwd URI `{cwd}` is not valid on this host: {source}"
)
}
Self::InvalidSandboxPolicyCwd { cwd, source } => write!(
f,
"sandbox policy cwd URI `{cwd}` is not valid on this host: {source}"
),
Self::MissingLinuxSandboxExecutable => {
write!(f, "missing codex-linux-sandbox executable path")
}
@@ -147,7 +172,19 @@ impl std::fmt::Display for SandboxTransformError {
}
}
impl std::error::Error for SandboxTransformError {}
impl std::error::Error for SandboxTransformError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidCommandCwd { source, .. }
| Self::InvalidSandboxPolicyCwd { source, .. } => Some(source),
Self::MissingLinuxSandboxExecutable => None,
#[cfg(target_os = "linux")]
Self::Wsl1UnsupportedForBubblewrap => None,
#[cfg(not(target_os = "macos"))]
Self::SeatbeltUnavailable => None,
}
}
}
#[derive(Default)]
pub struct SandboxManager;
@@ -202,6 +239,18 @@ impl SandboxManager {
windows_sandbox_level,
windows_sandbox_private_desktop,
} = request;
let native_command_cwd = command.cwd.to_abs_path().map_err(|source| {
SandboxTransformError::InvalidCommandCwd {
cwd: command.cwd.clone(),
source,
}
})?;
let native_sandbox_policy_cwd = sandbox_policy_cwd.to_abs_path().map_err(|source| {
SandboxTransformError::InvalidSandboxPolicyCwd {
cwd: sandbox_policy_cwd.clone(),
source,
}
})?;
let additional_permissions = command.additional_permissions.take();
let managed_mitm_ca_trust_bundle_path =
network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path);
@@ -210,7 +259,7 @@ impl SandboxManager {
let effective_permission_profile = with_managed_mitm_ca_readable_root(
effective_permission_profile,
managed_mitm_ca_trust_bundle_path.as_ref(),
sandbox_policy_cwd,
native_sandbox_policy_cwd.as_path(),
);
let (effective_file_system_policy, effective_network_policy) =
effective_permission_profile.to_runtime_permissions();
@@ -230,7 +279,7 @@ impl SandboxManager {
command: os_argv_to_strings(argv),
file_system_sandbox_policy: &effective_file_system_policy,
network_sandbox_policy: effective_network_policy,
sandbox_policy_cwd,
sandbox_policy_cwd: native_sandbox_policy_cwd.as_path(),
enforce_managed_network,
network,
extra_allow_unix_sockets: &[],
@@ -255,9 +304,9 @@ impl SandboxManager {
)?;
let mut args = create_linux_sandbox_command_args_for_permission_profile(
os_argv_to_strings(argv),
command.cwd.as_path(),
native_command_cwd.as_path(),
&effective_permission_profile,
sandbox_policy_cwd,
native_sandbox_policy_cwd.as_path(),
use_legacy_landlock,
allow_proxy_network,
);
@@ -274,7 +323,8 @@ impl SandboxManager {
Ok(SandboxExecRequest {
command: argv,
cwd: command.cwd,
cwd: native_command_cwd,
sandbox_policy_cwd: native_sandbox_policy_cwd,
env: command.env,
network: network.cloned(),
sandbox,