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

+38 -3
View File
@@ -17,6 +17,7 @@ use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
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 std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
@@ -39,11 +40,45 @@ impl TurnSkillsContext {
pub(crate) struct TurnEnvironment {
pub(crate) environment_id: String,
pub(crate) environment: Arc<Environment>,
pub(crate) cwd: AbsolutePathBuf,
// Keep both representations together while cwd consumers migrate to URI semantics. Keeping
// them synchronized means neither representation can be exposed through a mutable reference;
// updates must rebuild the validated pair through `TurnEnvironment::new`. Once
// `TurnEnvironment::cwd` itself becomes a `PathUri`, convert only at native filesystem and
// process-launch boundaries and remove this paired migration state.
cwd: AbsolutePathBuf,
cwd_uri: PathUri,
pub(crate) shell: Option<shell::Shell>,
}
impl TurnEnvironment {
pub(crate) fn new(
environment_id: String,
environment: Arc<Environment>,
cwd: AbsolutePathBuf,
shell: Option<shell::Shell>,
) -> CodexResult<Self> {
let cwd_uri = PathUri::from_abs_path(&cwd).map_err(|_| {
CodexErr::InvalidRequest(
"turn environment cwd cannot be represented as a file URI".to_string(),
)
})?;
Ok(Self {
environment_id,
environment,
cwd,
cwd_uri,
shell,
})
}
pub(crate) fn cwd(&self) -> &AbsolutePathBuf {
&self.cwd
}
pub(crate) fn cwd_uri(&self) -> &PathUri {
&self.cwd_uri
}
pub(crate) fn selection(&self) -> TurnEnvironmentSelection {
TurnEnvironmentSelection {
environment_id: self.environment_id.clone(),
@@ -291,7 +326,7 @@ impl TurnContext {
pub(crate) fn file_system_sandbox_context(
&self,
additional_permissions: Option<AdditionalPermissionProfile>,
cwd: &AbsolutePathBuf,
cwd: &PathUri,
) -> FileSystemSandboxContext {
let (base_file_system_sandbox_policy, base_network_sandbox_policy) =
self.permission_profile.to_runtime_permissions();
@@ -712,7 +747,7 @@ impl Session {
let primary_turn_environment = turn_environments.primary().cloned();
let cwd = primary_turn_environment
.as_ref()
.map(|turn_environment| turn_environment.cwd.clone())
.map(|turn_environment| turn_environment.cwd().clone())
.unwrap_or_else(|| session_configuration.cwd().clone());
let per_turn_config = Self::build_per_turn_config(&session_configuration, cwd.clone());
{