Thread Windows metadata targets through sessions

This commit is contained in:
Eva Wong
2026-05-04 10:05:45 -07:00
parent 16cc0e9c05
commit e62c735e1a
9 changed files with 162 additions and 29 deletions

View File

@@ -372,6 +372,7 @@ async fn run_command_under_windows_session(
None,
/*tty*/ false,
/*stdin_open*/ true,
&[],
config.permissions.windows_sandbox_private_desktop,
)
.await
@@ -386,6 +387,7 @@ async fn run_command_under_windows_session(
None,
/*tty*/ false,
/*stdin_open*/ true,
&[],
config.permissions.windows_sandbox_private_desktop,
)
.await

View File

@@ -407,39 +407,18 @@ pub fn build_exec_request(
ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_policy_cwd)
})
.map_err(CodexErr::from)?;
let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend(
exec_req.windows_sandbox_level,
exec_req.network.is_some(),
);
let sandbox_policy = exec_req.compatibility_sandbox_policy();
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
sandbox_cwd,
use_windows_elevated_backend,
)
} else {
resolve_windows_restricted_token_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
sandbox_cwd,
exec_req.windows_sandbox_level,
)
}
.map_err(CodexErr::UnsupportedOperation)?;
ensure_windows_sandbox_filesystem_overrides(&mut exec_req)
.map_err(CodexErr::UnsupportedOperation)?;
Ok(exec_req)
}
pub(crate) async fn execute_exec_request(
exec_request: ExecRequest,
mut exec_request: ExecRequest,
stdout_stream: Option<StdoutStream>,
after_spawn: Option<Box<dyn FnOnce() + Send>>,
) -> Result<ExecToolCallOutput> {
ensure_windows_sandbox_filesystem_overrides(&mut exec_request)
.map_err(CodexErr::UnsupportedOperation)?;
let sandbox_policy = exec_request.compatibility_sandbox_policy();
let ExecRequest {
command,
@@ -489,6 +468,36 @@ pub(crate) async fn execute_exec_request(
finalize_exec_result(raw_output_result, sandbox, duration)
}
pub(crate) fn ensure_windows_sandbox_filesystem_overrides(
exec_req: &mut ExecRequest,
) -> std::result::Result<(), String> {
let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend(
exec_req.windows_sandbox_level,
exec_req.network.is_some(),
);
let sandbox_policy = exec_req.compatibility_sandbox_policy();
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
&exec_req.windows_sandbox_policy_cwd,
use_windows_elevated_backend,
)
} else {
resolve_windows_restricted_token_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
&exec_req.windows_sandbox_policy_cwd,
exec_req.windows_sandbox_level,
)
}?;
Ok(())
}
async fn get_raw_output_result(
params: ExecParams,
network_sandbox_policy: NetworkSandboxPolicy,

View File

@@ -41,7 +41,7 @@ pub(crate) struct ExecServerEnvConfig {
pub(crate) local_policy_env: HashMap<String, String>,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ExecRequest {
pub command: Vec<String>,
pub cwd: AbsolutePathBuf,

View File

@@ -11,6 +11,8 @@ use tokio::time::Duration;
use tokio::time::Instant;
use tokio_util::sync::CancellationToken;
#[cfg(target_os = "windows")]
use crate::exec::WindowsProtectedMetadataMode;
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
use crate::exec_env::create_env;
use crate::exec_policy::ExecApprovalRequest;
@@ -163,6 +165,45 @@ fn exec_server_params_for_request(
}
}
fn prepare_exec_request_for_open_session(
request: &ExecRequest,
) -> Result<ExecRequest, UnifiedExecError> {
let mut request = request.clone();
crate::exec::ensure_windows_sandbox_filesystem_overrides(&mut request)
.map_err(UnifiedExecError::create_process)?;
Ok(request)
}
#[cfg(target_os = "windows")]
fn protected_metadata_targets_for_windows_session(
request: &ExecRequest,
) -> Vec<codex_windows_sandbox::ProtectedMetadataTarget> {
request
.windows_sandbox_filesystem_overrides
.as_ref()
.map(|overrides| {
overrides
.protected_metadata_targets
.iter()
.map(|target| {
let mode = match target.mode {
WindowsProtectedMetadataMode::ExistingDeny => {
codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny
}
WindowsProtectedMetadataMode::MissingCreationMonitor => {
codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor
}
};
codex_windows_sandbox::ProtectedMetadataTarget {
path: target.path.to_path_buf(),
mode,
}
})
.collect()
})
.unwrap_or_default()
}
/// Borrowed process state prepared for a `write_stdin` or poll operation.
struct PreparedProcessHandles {
process: Arc<UnifiedExecProcess>,
@@ -873,6 +914,7 @@ impl UnifiedExecProcessManager {
mut spawn_lifecycle: SpawnLifecycleHandle,
environment: &codex_exec_server::Environment,
) -> Result<UnifiedExecProcess, UnifiedExecError> {
let request = prepare_exec_request_for_open_session(request)?;
let inherited_fds = spawn_lifecycle.inherited_fds();
#[cfg(target_os = "windows")]
@@ -888,6 +930,8 @@ impl UnifiedExecProcessManager {
"windows sandbox: failed to resolve codex_home: {err}"
))
})?;
let protected_metadata_targets =
protected_metadata_targets_for_windows_session(&request);
let spawned = match request.windows_sandbox_level {
codex_protocol::config_types::WindowsSandboxLevel::Elevated => {
codex_windows_sandbox::spawn_windows_sandbox_session_elevated(
@@ -900,6 +944,7 @@ impl UnifiedExecProcessManager {
None,
tty,
tty,
&protected_metadata_targets,
request.windows_sandbox_private_desktop,
)
.await
@@ -916,6 +961,7 @@ impl UnifiedExecProcessManager {
None,
tty,
tty,
&protected_metadata_targets,
request.windows_sandbox_private_desktop,
)
.await
@@ -938,7 +984,7 @@ impl UnifiedExecProcessManager {
let started = environment
.get_exec_backend()
.start(exec_server_params_for_request(process_id, request, tty))
.start(exec_server_params_for_request(process_id, &request, tty))
.await
.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
spawn_lifecycle.after_spawn();

View File

@@ -135,6 +135,68 @@ fn exec_server_process_id_matches_unified_exec_process_id() {
assert_eq!(exec_server_process_id(/*process_id*/ 4321), "4321");
}
#[test]
fn open_session_prepares_windows_metadata_overrides_for_unified_exec() {
let temp_dir = tempfile::TempDir::new().expect("tempdir");
let cwd: codex_utils_absolute_path::AbsolutePathBuf = dunce::canonicalize(temp_dir.path())
.expect("canonical temp dir")
.try_into()
.expect("absolute temp dir");
let permission_profile = codex_protocol::models::PermissionProfile::workspace_write_with(
&[],
codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ true,
/*exclude_slash_tmp*/ true,
);
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
let request = ExecRequest {
command: vec![
"cmd.exe".to_string(),
"/c".to_string(),
"echo ok".to_string(),
],
cwd: cwd.clone(),
env: HashMap::new(),
exec_server_env_config: None,
network: None,
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
sandbox: codex_sandboxing::SandboxType::WindowsRestrictedToken,
windows_sandbox_policy_cwd: cwd.clone(),
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken,
windows_sandbox_private_desktop: false,
permission_profile,
file_system_sandbox_policy,
network_sandbox_policy,
windows_sandbox_filesystem_overrides: None,
arg0: None,
};
let prepared = prepare_exec_request_for_open_session(&request).expect("prepare request");
let overrides = prepared
.windows_sandbox_filesystem_overrides
.expect("metadata overrides");
assert_eq!(
overrides.protected_metadata_targets,
vec![
crate::exec::WindowsProtectedMetadataTarget {
path: cwd.join(".agents"),
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
},
crate::exec::WindowsProtectedMetadataTarget {
path: cwd.join(".codex"),
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
},
crate::exec::WindowsProtectedMetadataTarget {
path: cwd.join(".git"),
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
},
]
);
}
#[tokio::test]
async fn network_denial_fallback_message_names_sandbox_network_proxy() {
let message = network_denial_message_for_session(/*session*/ None, /*deferred*/ None).await;

View File

@@ -8,6 +8,7 @@ use crate::ipc_framed::FramedMessage;
use crate::ipc_framed::Message;
use crate::ipc_framed::SpawnRequest;
use crate::runner_client::spawn_runner_transport;
use crate::setup::ProtectedMetadataTarget;
use crate::spawn_prep::prepare_elevated_spawn_context;
use anyhow::Result;
use codex_utils_pty::ProcessDriver;
@@ -29,6 +30,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated(
timeout_ms: Option<u64>,
tty: bool,
stdin_open: bool,
protected_metadata_targets: &[ProtectedMetadataTarget],
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
let elevated = prepare_elevated_spawn_context(
@@ -38,7 +40,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated(
cwd,
&mut env_map,
&command,
&[],
protected_metadata_targets,
)?;
let spawn_request = SpawnRequest {

View File

@@ -10,6 +10,7 @@ use crate::process::StderrMode;
use crate::process::StdinMode;
use crate::process::read_handle_loop;
use crate::process::spawn_process_with_pipes;
use crate::setup::ProtectedMetadataTarget;
use crate::spawn_prep::LocalSid;
use crate::spawn_prep::allow_null_device_for_workspace_write;
use crate::spawn_prep::apply_legacy_session_acl_rules;
@@ -289,6 +290,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
timeout_ms: Option<u64>,
tty: bool,
stdin_open: bool,
_protected_metadata_targets: &[ProtectedMetadataTarget],
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
let common = prepare_legacy_spawn_context(

View File

@@ -9,6 +9,7 @@
mod backends;
use crate::setup::ProtectedMetadataTarget;
use anyhow::Result;
use codex_utils_pty::SpawnedProcess;
use std::collections::HashMap;
@@ -25,6 +26,7 @@ pub async fn spawn_windows_sandbox_session_legacy(
timeout_ms: Option<u64>,
tty: bool,
stdin_open: bool,
protected_metadata_targets: &[ProtectedMetadataTarget],
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
backends::legacy::spawn_windows_sandbox_session_legacy(
@@ -37,6 +39,7 @@ pub async fn spawn_windows_sandbox_session_legacy(
timeout_ms,
tty,
stdin_open,
protected_metadata_targets,
use_private_desktop,
)
.await
@@ -53,6 +56,7 @@ pub async fn spawn_windows_sandbox_session_elevated(
timeout_ms: Option<u64>,
tty: bool,
stdin_open: bool,
protected_metadata_targets: &[ProtectedMetadataTarget],
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
backends::elevated::spawn_windows_sandbox_session_elevated(
@@ -65,6 +69,7 @@ pub async fn spawn_windows_sandbox_session_elevated(
timeout_ms,
tty,
stdin_open,
protected_metadata_targets,
use_private_desktop,
)
.await

View File

@@ -162,6 +162,7 @@ fn legacy_non_tty_cmd_emits_output() {
Some(5_000),
/*tty*/ false,
/*stdin_open*/ false,
&[],
/*use_private_desktop*/ true,
)
.await
@@ -202,6 +203,7 @@ fn legacy_non_tty_powershell_emits_output() {
Some(5_000),
/*tty*/ false,
/*stdin_open*/ false,
&[],
/*use_private_desktop*/ true,
)
.await
@@ -426,6 +428,7 @@ fn legacy_tty_powershell_emits_output_and_accepts_input() {
Some(10_000),
/*tty*/ true,
/*stdin_open*/ true,
&[],
/*use_private_desktop*/ true,
)
.await
@@ -474,6 +477,7 @@ fn legacy_tty_cmd_emits_output_and_accepts_input() {
Some(10_000),
/*tty*/ true,
/*stdin_open*/ true,
&[],
/*use_private_desktop*/ true,
)
.await
@@ -525,6 +529,7 @@ fn legacy_tty_cmd_default_desktop_emits_output_and_accepts_input() {
Some(10_000),
/*tty*/ true,
/*stdin_open*/ true,
&[],
/*use_private_desktop*/ false,
)
.await