mirror of
https://github.com/openai/codex.git
synced 2026-10-20 08:19:50 +00:00
Support Windows sandboxing in the exec server (#34423)
## Why Sandboxed process launch through the exec server was unsupported on Windows. ## What changed - Add a shared native process launcher that selects the Windows sandbox session backend when required and otherwise launches PTY or pipe-based processes. - Pass Windows permission, workspace-root, proxy, filesystem-override, and desktop settings through exec-server process preparation. - Preserve piped stdin and inherited file descriptors across the shared launch path. ## Testing - Verify that a remote Windows sandbox process accepts stdin while blocking a write under a read-only policy. - Enable workspace-root sandbox tests on native Windows while continuing to skip environments that cannot emulate Windows sandbox semantics. GitOrigin-RevId: 6719ccbe87ad0d5a57af56503e3f23f894c60a71
This commit is contained in:
Generated
+1
@@ -3870,6 +3870,7 @@ dependencies = [
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-home-dir",
|
||||
"codex-utils-path-uri",
|
||||
"codex-utils-pty",
|
||||
"codex-windows-sandbox",
|
||||
"dunce",
|
||||
"libc",
|
||||
|
||||
@@ -275,13 +275,22 @@ impl CommandExecManager {
|
||||
&env,
|
||||
&arg0,
|
||||
size.unwrap_or_default(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
} else if stream_stdin {
|
||||
codex_utils_pty::spawn_pipe_process(program, args, cwd.as_path(), &env, &arg0).await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(program, args, cwd.as_path(), &env, &arg0)
|
||||
codex_utils_pty::spawn_pipe_process(program, args, cwd.as_path(), &env, &arg0, &[])
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
cwd.as_path(),
|
||||
&env,
|
||||
&arg0,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
};
|
||||
let spawned = match spawned {
|
||||
Ok(spawned) => spawned,
|
||||
|
||||
@@ -312,13 +312,22 @@ impl ProcessExecManager {
|
||||
&env,
|
||||
&arg0,
|
||||
size.unwrap_or_default(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
} else if stream_stdin {
|
||||
codex_utils_pty::spawn_pipe_process(program, args, cwd.as_path(), &env, &arg0).await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(program, args, cwd.as_path(), &env, &arg0)
|
||||
codex_utils_pty::spawn_pipe_process(program, args, cwd.as_path(), &env, &arg0, &[])
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
cwd.as_path(),
|
||||
&env,
|
||||
&arg0,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
};
|
||||
let spawned = match spawned {
|
||||
Ok(spawned) => spawned,
|
||||
|
||||
@@ -1004,92 +1004,6 @@ impl UnifiedExecProcessManager {
|
||||
) -> Result<UnifiedExecProcess, UnifiedExecError> {
|
||||
let inherited_fds = spawn_lifecycle.inherited_fds();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if request.sandbox == codex_sandboxing::SandboxType::WindowsRestrictedToken {
|
||||
// TODO(anp): Keep PathUri through the Windows sandbox launch boundary.
|
||||
let native_cwd =
|
||||
request
|
||||
.cwd
|
||||
.to_abs_path()
|
||||
.map_err(|_| UnifiedExecError::ForeignPath {
|
||||
path: request.cwd.clone(),
|
||||
})?;
|
||||
let codex_home = crate::config::find_codex_home().map_err(|err| {
|
||||
UnifiedExecError::create_process(format!(
|
||||
"windows sandbox: failed to resolve codex_home: {err}"
|
||||
))
|
||||
})?;
|
||||
let additional_deny_write_paths = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.map(|overrides| overrides.additional_deny_write_paths.clone())
|
||||
.unwrap_or_default();
|
||||
let additional_deny_read_paths = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.map(|overrides| overrides.additional_deny_read_paths.clone())
|
||||
.unwrap_or_default();
|
||||
let elevated_read_roots_override = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.and_then(|overrides| overrides.read_roots_override.clone());
|
||||
let elevated_read_roots_include_platform_defaults = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.is_some_and(|overrides| overrides.read_roots_include_platform_defaults);
|
||||
let elevated_write_roots_override = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.and_then(|overrides| overrides.write_roots_override.clone());
|
||||
let spawned = match request.windows_sandbox_level {
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Elevated => {
|
||||
codex_windows_sandbox::spawn_windows_sandbox_session_elevated_for_permission_profile(
|
||||
&request.permission_profile,
|
||||
request.windows_sandbox_workspace_roots.as_slice(),
|
||||
codex_home.as_ref(),
|
||||
request.command.clone(),
|
||||
native_cwd.as_path(),
|
||||
request.env.clone(),
|
||||
request.network.is_some(),
|
||||
None,
|
||||
elevated_read_roots_override.as_deref(),
|
||||
elevated_read_roots_include_platform_defaults,
|
||||
elevated_write_roots_override.as_deref(),
|
||||
&additional_deny_read_paths,
|
||||
&additional_deny_write_paths,
|
||||
tty,
|
||||
tty,
|
||||
request.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
}
|
||||
codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken
|
||||
| codex_protocol::config_types::WindowsSandboxLevel::Disabled => {
|
||||
codex_windows_sandbox::spawn_windows_sandbox_session_legacy(
|
||||
&request.permission_profile,
|
||||
request.windows_sandbox_workspace_roots.as_slice(),
|
||||
codex_home.as_ref(),
|
||||
request.command.clone(),
|
||||
native_cwd.as_path(),
|
||||
request.env.clone(),
|
||||
None,
|
||||
&additional_deny_read_paths,
|
||||
&additional_deny_write_paths,
|
||||
tty,
|
||||
tty,
|
||||
request.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
spawn_lifecycle.after_spawn();
|
||||
return UnifiedExecProcess::from_spawned(
|
||||
spawned.map_err(|err| UnifiedExecError::create_process(err.to_string()))?,
|
||||
request.sandbox,
|
||||
spawn_lifecycle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if environment.is_remote() {
|
||||
if !inherited_fds.is_empty() {
|
||||
return Err(UnifiedExecError::create_process(
|
||||
@@ -1114,35 +1028,39 @@ impl UnifiedExecProcessManager {
|
||||
path: request.cwd.clone(),
|
||||
})?;
|
||||
|
||||
let (program, args) = request
|
||||
.command
|
||||
.split_first()
|
||||
.ok_or(UnifiedExecError::MissingCommandLine)?;
|
||||
let spawn_result = if tty {
|
||||
codex_utils_pty::pty::spawn_process_with_inherited_fds(
|
||||
program,
|
||||
args,
|
||||
native_cwd.as_path(),
|
||||
&request.env,
|
||||
&request.arg0,
|
||||
codex_utils_pty::TerminalSize::default(),
|
||||
&inherited_fds,
|
||||
)
|
||||
.await
|
||||
if request.command.is_empty() {
|
||||
return Err(UnifiedExecError::MissingCommandLine);
|
||||
}
|
||||
let windows_sandbox = if request.sandbox
|
||||
== codex_sandboxing::SandboxType::WindowsRestrictedToken
|
||||
{
|
||||
Some(codex_sandboxing::WindowsSandboxSpawnRequest {
|
||||
permission_profile: &request.permission_profile,
|
||||
workspace_roots: &request.windows_sandbox_workspace_roots,
|
||||
windows_sandbox_level: request.windows_sandbox_level,
|
||||
proxy_enforced: request.network.is_some(),
|
||||
proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
|
||||
filesystem_overrides: request.windows_sandbox_filesystem_overrides.as_ref(),
|
||||
use_private_desktop: request.windows_sandbox_private_desktop,
|
||||
})
|
||||
} else {
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin_with_inherited_fds(
|
||||
program,
|
||||
args,
|
||||
native_cwd.as_path(),
|
||||
&request.env,
|
||||
&request.arg0,
|
||||
&inherited_fds,
|
||||
)
|
||||
.await
|
||||
None
|
||||
};
|
||||
let spawn_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
|
||||
command: &request.command,
|
||||
cwd: native_cwd.as_path(),
|
||||
env: &request.env,
|
||||
arg0: &request.arg0,
|
||||
sandbox: request.sandbox,
|
||||
windows_sandbox,
|
||||
tty,
|
||||
stdin_open: tty,
|
||||
inherited_fds: &inherited_fds,
|
||||
})
|
||||
.await;
|
||||
spawn_lifecycle.after_spawn();
|
||||
let spawned =
|
||||
spawn_result.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
|
||||
spawn_lifecycle.after_spawn();
|
||||
UnifiedExecProcess::from_spawned(spawned, request.sandbox, spawn_lifecycle).await
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
#[cfg(windows)]
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::TestTargetOs;
|
||||
use core_test_support::responses::ResponseMock;
|
||||
use core_test_support::responses::ev_apply_patch_custom_tool_call;
|
||||
@@ -15,7 +17,7 @@ use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_sequence;
|
||||
use core_test_support::responses::sse;
|
||||
use core_test_support::responses::start_mock_server;
|
||||
use core_test_support::skip_if_target_windows;
|
||||
use core_test_support::skip_if_wine_exec;
|
||||
use core_test_support::test_codex::TestCodex;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use core_test_support::test_target_os;
|
||||
@@ -36,12 +38,19 @@ fn workspace_roots_profile() -> PermissionProfile {
|
||||
|
||||
async fn workspace_roots_test(server: &MockServer) -> Result<TestCodex> {
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
config.cwd = dunce::canonicalize(config.cwd.as_path())
|
||||
.expect("test workspace should be canonicalizable")
|
||||
.abs();
|
||||
}
|
||||
config.use_experimental_unified_exec_tool = true;
|
||||
config
|
||||
.features
|
||||
.enable(Feature::UnifiedExec)
|
||||
.expect("test config should allow feature update");
|
||||
config.workspace_roots = vec![config.cwd.clone()];
|
||||
config.set_windows_sandbox_enabled(/*value*/ true);
|
||||
});
|
||||
builder.build_with_auto_env(server).await
|
||||
}
|
||||
@@ -60,10 +69,7 @@ fn command_arguments(path: &str, contents: &str) -> Result<String> {
|
||||
TestTargetOs::Linux | TestTargetOs::MacOs => {
|
||||
("bash", format!("printf %s '{contents}' > '{path}'"))
|
||||
}
|
||||
TestTargetOs::Windows => (
|
||||
"powershell",
|
||||
format!("Set-Content -NoNewline -Path '{path}' -Value '{contents}'"),
|
||||
),
|
||||
TestTargetOs::Windows => ("cmd", format!("echo {contents}>{path}")),
|
||||
};
|
||||
Ok(serde_json::to_string(&json!({
|
||||
"cmd": command,
|
||||
@@ -134,9 +140,9 @@ async fn workspace_roots_allow_file_and_command_writes() -> Result<()> {
|
||||
const PATCH_CONTENTS: &str = "workspace root patch access";
|
||||
const COMMAND_CONTENTS: &str = "workspace root command access";
|
||||
|
||||
skip_if_target_windows!(
|
||||
skip_if_wine_exec!(
|
||||
Ok(()),
|
||||
"sandboxed process launch is not supported by the exec-server Windows backend"
|
||||
"Wine does not emulate Windows restricted-token and ACL sandbox semantics"
|
||||
);
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -173,7 +179,10 @@ async fn workspace_roots_allow_file_and_command_writes() -> Result<()> {
|
||||
read_file(&test, &patch_path).await?,
|
||||
format!("{PATCH_CONTENTS}\n")
|
||||
);
|
||||
assert_eq!(read_file(&test, &command_path).await?, COMMAND_CONTENTS);
|
||||
assert_eq!(
|
||||
read_file(&test, &command_path).await?.trim_end(),
|
||||
COMMAND_CONTENTS
|
||||
);
|
||||
|
||||
remove_files(&test, &[&patch_path, &command_path]).await
|
||||
}
|
||||
@@ -183,19 +192,24 @@ async fn workspace_roots_deny_file_and_command_writes_outside_roots() -> Result<
|
||||
const PATCH_CONTENTS: &str = "outside workspace root patch";
|
||||
const COMMAND_CONTENTS: &str = "outside workspace root command";
|
||||
|
||||
skip_if_target_windows!(
|
||||
skip_if_wine_exec!(
|
||||
Ok(()),
|
||||
"sandboxed process launch is not supported by the exec-server Windows backend"
|
||||
"Wine does not emulate Windows restricted-token and ACL sandbox semantics"
|
||||
);
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let test = workspace_roots_test(&server).await?;
|
||||
let patch_path = outside_workspace_path(&test, "outside-patch.txt")?;
|
||||
let command_path = outside_workspace_path(&test, "outside-command.txt")?;
|
||||
let patch_path_display = patch_path.inferred_native_path_string();
|
||||
let patch_relative_path = format!(
|
||||
"../{}",
|
||||
patch_path
|
||||
.basename()
|
||||
.context("outside patch path should have a file name")?
|
||||
);
|
||||
let command_path_display = command_path.inferred_native_path_string();
|
||||
let patch = format!(
|
||||
"*** Begin Patch\n*** Add File: {patch_path_display}\n+{PATCH_CONTENTS}\n*** End Patch\n"
|
||||
"*** Begin Patch\n*** Add File: {patch_relative_path}\n+{PATCH_CONTENTS}\n*** End Patch\n"
|
||||
);
|
||||
|
||||
let response_mock =
|
||||
@@ -222,8 +236,9 @@ async fn workspace_roots_deny_file_and_command_writes_outside_roots() -> Result<
|
||||
.context("denied command result should be present")?;
|
||||
let command_output = command_output.context("denied command output should be present")?;
|
||||
assert!(
|
||||
command_output.contains(&command_path_display),
|
||||
"denied command output should identify {command_path_display}, got {command_output:?}"
|
||||
command_output.contains("Access is denied")
|
||||
|| command_output.contains(&command_path_display),
|
||||
"outside command should be denied, got {command_output:?}"
|
||||
);
|
||||
assert!(
|
||||
test.fs()
|
||||
|
||||
@@ -18,7 +18,6 @@ use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::is_likely_sandbox_denied;
|
||||
use codex_utils_pty::ExecCommandSession;
|
||||
use codex_utils_pty::ProcessSignal as PtyProcessSignal;
|
||||
use codex_utils_pty::TerminalSize;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::Notify;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -241,10 +240,9 @@ impl LocalProcess {
|
||||
let process_id = params.process_id.clone();
|
||||
let prepared =
|
||||
prepare_exec_request(¶ms, child_env(¶ms), self.runtime_paths.as_ref()).await?;
|
||||
let (program, args) = prepared
|
||||
.command
|
||||
.split_first()
|
||||
.ok_or_else(|| invalid_params("argv must not be empty".to_string()))?;
|
||||
if prepared.command.is_empty() {
|
||||
return Err(invalid_params("argv must not be empty".to_string()));
|
||||
}
|
||||
|
||||
let start = Arc::new(ProcessStart);
|
||||
{
|
||||
@@ -260,35 +258,18 @@ impl LocalProcess {
|
||||
);
|
||||
}
|
||||
|
||||
let spawned_result = if params.tty {
|
||||
codex_utils_pty::spawn_pty_process(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
TerminalSize::default(),
|
||||
)
|
||||
.await
|
||||
} else if params.pipe_stdin {
|
||||
codex_utils_pty::spawn_pipe_process(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
)
|
||||
.await
|
||||
};
|
||||
let spawned_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
|
||||
command: &prepared.command,
|
||||
cwd: prepared.cwd.as_path(),
|
||||
env: &prepared.env,
|
||||
arg0: &prepared.arg0,
|
||||
sandbox: prepared.sandbox,
|
||||
windows_sandbox: prepared.windows_sandbox_spawn_request(),
|
||||
tty: params.tty,
|
||||
stdin_open: params.tty || params.pipe_stdin,
|
||||
inherited_fds: &[],
|
||||
})
|
||||
.await;
|
||||
let spawned = match spawned_result {
|
||||
Ok(spawned) => spawned,
|
||||
Err(err) => {
|
||||
|
||||
@@ -16,6 +16,12 @@ use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxTransformRequest;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_sandboxing::WindowsSandboxFilesystemOverrides;
|
||||
use codex_sandboxing::WindowsSandboxProxySettingsMode;
|
||||
use codex_sandboxing::WindowsSandboxSpawnRequest;
|
||||
use codex_sandboxing::resolve_windows_elevated_filesystem_overrides;
|
||||
use codex_sandboxing::resolve_windows_restricted_token_filesystem_overrides;
|
||||
use codex_sandboxing::windows_sandbox_uses_elevated_backend;
|
||||
use codex_sandboxing::with_managed_mitm_ca_readable_root;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -32,6 +38,32 @@ pub(crate) struct PreparedExecRequest {
|
||||
pub(crate) arg0: Option<String>,
|
||||
pub(crate) sandbox: SandboxType,
|
||||
pub(crate) network_proxy_handle: Option<NetworkProxyHandle>,
|
||||
windows_sandbox: Option<PreparedWindowsSandboxRequest>,
|
||||
}
|
||||
|
||||
struct PreparedWindowsSandboxRequest {
|
||||
permission_profile: PermissionProfile,
|
||||
workspace_roots: Vec<AbsolutePathBuf>,
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
|
||||
proxy_enforced: bool,
|
||||
filesystem_overrides: Option<WindowsSandboxFilesystemOverrides>,
|
||||
use_private_desktop: bool,
|
||||
}
|
||||
|
||||
impl PreparedExecRequest {
|
||||
pub(crate) fn windows_sandbox_spawn_request(&self) -> Option<WindowsSandboxSpawnRequest<'_>> {
|
||||
self.windows_sandbox
|
||||
.as_ref()
|
||||
.map(|request| WindowsSandboxSpawnRequest {
|
||||
permission_profile: &request.permission_profile,
|
||||
workspace_roots: &request.workspace_roots,
|
||||
windows_sandbox_level: request.windows_sandbox_level,
|
||||
proxy_enforced: request.proxy_enforced,
|
||||
proxy_settings_mode: WindowsSandboxProxySettingsMode::Reconcile,
|
||||
filesystem_overrides: request.filesystem_overrides.as_ref(),
|
||||
use_private_desktop: request.use_private_desktop,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_exec_request(
|
||||
@@ -53,6 +85,7 @@ pub(crate) async fn prepare_exec_request(
|
||||
arg0: params.arg0.clone(),
|
||||
sandbox: SandboxType::None,
|
||||
network_proxy_handle,
|
||||
windows_sandbox: None,
|
||||
});
|
||||
};
|
||||
let runtime_paths = runtime_paths
|
||||
@@ -112,54 +145,83 @@ pub(crate) async fn prepare_exec_request(
|
||||
sandbox_context.windows_sandbox_level,
|
||||
params.enforce_managed_network,
|
||||
);
|
||||
match sandbox {
|
||||
SandboxType::None => {
|
||||
return Err(invalid_params(
|
||||
"sandbox intent cannot be enforced on this executor".to_string(),
|
||||
));
|
||||
}
|
||||
SandboxType::WindowsRestrictedToken => {
|
||||
// TODO(jif): Launch generic remote commands through the Windows sandbox session API
|
||||
// while preserving argv and TTY behavior and passing the child environment out of band.
|
||||
return Err(invalid_params(
|
||||
"sandboxed remote process launch is not supported on Windows".to_string(),
|
||||
));
|
||||
}
|
||||
SandboxType::MacosSeatbelt | SandboxType::LinuxSeccomp => {}
|
||||
if sandbox == SandboxType::None {
|
||||
return Err(invalid_params(
|
||||
"sandbox intent cannot be enforced on this executor".to_string(),
|
||||
));
|
||||
}
|
||||
let (program, args) = params
|
||||
.argv
|
||||
.split_first()
|
||||
.ok_or_else(|| invalid_params("argv must not be empty".to_string()))?;
|
||||
let request = sandbox_manager
|
||||
.transform_for_direct_spawn(SandboxDirectSpawnTransformRequest {
|
||||
workspace_roots,
|
||||
windows_sandbox_proxy_settings_mode:
|
||||
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
|
||||
transform: SandboxTransformRequest {
|
||||
// TODO(jif): Preserve params.arg0 for the inner command across the sandbox
|
||||
// wrapper, or reject sandboxed requests with a custom arg0.
|
||||
command: SandboxCommand {
|
||||
program: program.into(),
|
||||
args: args.to_vec(),
|
||||
cwd: params.cwd.clone(),
|
||||
env,
|
||||
managed_network,
|
||||
additional_permissions: None,
|
||||
},
|
||||
permissions: &permissions,
|
||||
sandbox,
|
||||
enforce_managed_network: params.enforce_managed_network,
|
||||
environment_id: None,
|
||||
network: None,
|
||||
sandbox_policy_cwd,
|
||||
codex_linux_sandbox_exe: runtime_paths.codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
|
||||
let transform_request = SandboxDirectSpawnTransformRequest {
|
||||
workspace_roots,
|
||||
windows_sandbox_proxy_settings_mode:
|
||||
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
|
||||
transform: SandboxTransformRequest {
|
||||
// TODO(jif): Preserve params.arg0 for the inner command across the sandbox
|
||||
// wrapper, or reject sandboxed requests with a custom arg0.
|
||||
command: SandboxCommand {
|
||||
program: program.into(),
|
||||
args: args.to_vec(),
|
||||
cwd: params.cwd.clone(),
|
||||
env,
|
||||
managed_network,
|
||||
additional_permissions: None,
|
||||
},
|
||||
})
|
||||
permissions: &permissions,
|
||||
sandbox,
|
||||
enforce_managed_network: params.enforce_managed_network,
|
||||
environment_id: None,
|
||||
network: None,
|
||||
sandbox_policy_cwd,
|
||||
codex_linux_sandbox_exe: runtime_paths.codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
|
||||
},
|
||||
};
|
||||
let mut request = if sandbox == SandboxType::WindowsRestrictedToken {
|
||||
// The shared launcher invokes the native Windows session spawner directly.
|
||||
sandbox_manager.transform(transform_request.transform)
|
||||
} else {
|
||||
sandbox_manager.transform_for_direct_spawn(transform_request)
|
||||
}
|
||||
.map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?;
|
||||
let windows_sandbox = if sandbox == SandboxType::WindowsRestrictedToken {
|
||||
request.arg0 = params.arg0.clone();
|
||||
let proxy_enforced = params.enforce_managed_network;
|
||||
let use_elevated = windows_sandbox_uses_elevated_backend(
|
||||
sandbox_context.windows_sandbox_level,
|
||||
proxy_enforced,
|
||||
);
|
||||
let filesystem_overrides = if use_elevated {
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
sandbox,
|
||||
&permissions,
|
||||
&native_sandbox_policy_cwd,
|
||||
use_elevated,
|
||||
)
|
||||
} else {
|
||||
resolve_windows_restricted_token_filesystem_overrides(
|
||||
sandbox,
|
||||
&permissions,
|
||||
&native_sandbox_policy_cwd,
|
||||
sandbox_context.windows_sandbox_level,
|
||||
)
|
||||
}
|
||||
.map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?;
|
||||
Some(PreparedWindowsSandboxRequest {
|
||||
permission_profile: permissions,
|
||||
workspace_roots: native_workspace_roots,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
proxy_enforced,
|
||||
filesystem_overrides,
|
||||
use_private_desktop: sandbox_context.windows_sandbox_private_desktop,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(PreparedExecRequest {
|
||||
command: request.command,
|
||||
cwd: native_path(&request.cwd, "cwd")?,
|
||||
@@ -167,6 +229,7 @@ pub(crate) async fn prepare_exec_request(
|
||||
arg0: request.arg0,
|
||||
sandbox: request.sandbox,
|
||||
network_proxy_handle,
|
||||
windows_sandbox,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
#[cfg(unix)]
|
||||
#[cfg(windows)]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
#[cfg(any(unix, windows))]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -18,9 +20,9 @@ use tokio::time::timeout;
|
||||
|
||||
use super::prepare_exec_request;
|
||||
use crate::ExecParams;
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, windows))]
|
||||
use crate::ExecServerRuntimePaths;
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, windows))]
|
||||
use crate::FileSystemSandboxContext;
|
||||
use crate::ProcessId;
|
||||
|
||||
@@ -272,3 +274,50 @@ async fn disabled_remote_proxy_config_is_rejected_before_exporting_ports() {
|
||||
.contains("executor-local network proxy launch requires an enabled proxy")
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::test]
|
||||
async fn managed_network_selects_elevated_windows_spawn() {
|
||||
let cwd: AbsolutePathBuf = std::env::current_dir()
|
||||
.expect("current directory")
|
||||
.try_into()
|
||||
.expect("absolute cwd");
|
||||
let cwd_uri = PathUri::from_abs_path(&cwd);
|
||||
let self_exe = std::env::current_exe().expect("current executable");
|
||||
let runtime_paths = ExecServerRuntimePaths::new(self_exe, None).expect("runtime paths");
|
||||
let permissions = PermissionProfile::read_only();
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
permissions.clone(),
|
||||
cwd_uri.clone(),
|
||||
);
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
let params = ExecParams {
|
||||
process_id: ProcessId::from("process-managed-network"),
|
||||
argv: vec!["cmd.exe".to_string(), "/c".to_string(), "exit".to_string()],
|
||||
cwd: cwd_uri,
|
||||
env_policy: None,
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: true,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request(¶ms, HashMap::new(), Some(&runtime_paths))
|
||||
.await
|
||||
.expect("prepare sandboxed request");
|
||||
let spawn = prepared
|
||||
.windows_sandbox_spawn_request()
|
||||
.expect("Windows sandbox spawn request");
|
||||
|
||||
assert_eq!(
|
||||
spawn.windows_sandbox_level,
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
);
|
||||
assert!(spawn.proxy_enforced);
|
||||
assert_eq!(spawn.permission_profile, &permissions);
|
||||
assert_eq!(spawn.workspace_roots, std::slice::from_ref(&cwd));
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@ use codex_exec_server::ExecOutputStream;
|
||||
use codex_exec_server::ExecParams;
|
||||
use codex_exec_server::ExecProcess;
|
||||
use codex_exec_server::ExecProcessEvent;
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, windows))]
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::ProcessId;
|
||||
use codex_exec_server::ProcessSignal;
|
||||
use codex_exec_server::ReadResponse;
|
||||
use codex_exec_server::StartedExecProcess;
|
||||
use codex_exec_server::WriteStatus;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(unix)]
|
||||
@@ -34,6 +35,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
@@ -699,6 +701,65 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_remote_windows_sandbox_process_write() -> Result<()> {
|
||||
let context = create_process_context(/*use_remote*/ true).await?;
|
||||
let workspace = TempDir::new()?;
|
||||
let blocked_file = workspace.path().join("blocked.txt");
|
||||
let cwd = PathUri::from_host_native_path(workspace.path())?;
|
||||
let mut sandbox = FileSystemSandboxContext::from_legacy_sandbox_policy(
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
cwd.clone(),
|
||||
)?;
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
|
||||
let session = match context
|
||||
.backend
|
||||
.start(ExecParams {
|
||||
process_id: ProcessId::from("proc-windows-sandbox-stdin"),
|
||||
argv: vec![
|
||||
r"C:\Windows\System32\cmd.exe".to_string(),
|
||||
"/D".to_string(),
|
||||
"/V:ON".to_string(),
|
||||
"/S".to_string(),
|
||||
"/C".to_string(),
|
||||
format!(
|
||||
"set /P line= & echo blocked > \"{}\" & echo from-stdin:!line!",
|
||||
blocked_file.display()
|
||||
),
|
||||
],
|
||||
cwd,
|
||||
env_policy: /*env_policy*/ None,
|
||||
env: Default::default(),
|
||||
tty: false,
|
||||
pipe_stdin: true,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(session) => session,
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
let write_response = session.process.write(b"hello\n".to_vec()).await?;
|
||||
assert_eq!(write_response.status, WriteStatus::Accepted);
|
||||
let StartedExecProcess { process, .. } = session;
|
||||
let wake_rx = process.subscribe_wake();
|
||||
let (output, exit_code, closed) = collect_process_output_from_reads(process, wake_rx).await?;
|
||||
|
||||
assert!(
|
||||
output.contains("from-stdin:hello"),
|
||||
"unexpected output: {output:?}"
|
||||
);
|
||||
assert_eq!(exit_code, Some(0));
|
||||
assert!(closed);
|
||||
assert!(!blocked_file.exists());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_exec_process_rejects_write_without_pipe_stdin(use_remote: bool) -> Result<()> {
|
||||
let context = create_process_context(use_remote).await?;
|
||||
let process_id = "proc-stdin-closed".to_string();
|
||||
@@ -1098,6 +1159,13 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
|
||||
assert_exec_process_write_then_read_without_tty(use_remote).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[cfg_attr(not(windows), ignore = "Windows-only exec-server sandbox process test")]
|
||||
#[serial_test::serial(remote_exec_server)]
|
||||
async fn remote_windows_sandbox_process_accepts_process_write() -> Result<()> {
|
||||
assert_remote_windows_sandbox_process_write().await
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[cfg_attr(not(unix), ignore = "Unix-only exec-server process test")]
|
||||
|
||||
@@ -13,10 +13,12 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-network-proxy = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-path-uri = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
codex-windows-sandbox = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
|
||||
@@ -6,6 +6,7 @@ mod manager;
|
||||
pub mod policy_transforms;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod seatbelt;
|
||||
mod spawn;
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -25,6 +26,9 @@ pub use manager::SandboxablePreference;
|
||||
pub use manager::compatibility_sandbox_policy_for_permission_profile;
|
||||
pub use manager::get_platform_sandbox;
|
||||
pub use manager::with_managed_mitm_ca_readable_root;
|
||||
pub use spawn::SpawnRequest;
|
||||
pub use spawn::WindowsSandboxSpawnRequest;
|
||||
pub use spawn::spawn_process;
|
||||
pub use windows::WindowsSandboxFilesystemOverrides;
|
||||
pub use windows::permission_profile_supports_windows_restricted_token_sandbox;
|
||||
pub use windows::resolve_windows_elevated_filesystem_overrides;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_pty::SpawnedProcess;
|
||||
use codex_utils_pty::TerminalSize;
|
||||
|
||||
use crate::SandboxType;
|
||||
use crate::WindowsSandboxFilesystemOverrides;
|
||||
use crate::WindowsSandboxProxySettingsMode;
|
||||
|
||||
/// Windows-specific inputs for an executor-native process spawn.
|
||||
pub struct WindowsSandboxSpawnRequest<'a> {
|
||||
pub permission_profile: &'a PermissionProfile,
|
||||
pub workspace_roots: &'a [AbsolutePathBuf],
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub proxy_enforced: bool,
|
||||
pub proxy_settings_mode: WindowsSandboxProxySettingsMode,
|
||||
pub filesystem_overrides: Option<&'a WindowsSandboxFilesystemOverrides>,
|
||||
pub use_private_desktop: bool,
|
||||
}
|
||||
|
||||
/// Executor-native process launch request shared by local and exec-server execution.
|
||||
pub struct SpawnRequest<'a> {
|
||||
pub command: &'a [String],
|
||||
pub cwd: &'a Path,
|
||||
pub env: &'a HashMap<String, String>,
|
||||
pub arg0: &'a Option<String>,
|
||||
pub sandbox: SandboxType,
|
||||
pub windows_sandbox: Option<WindowsSandboxSpawnRequest<'a>>,
|
||||
pub tty: bool,
|
||||
pub stdin_open: bool,
|
||||
pub inherited_fds: &'a [i32],
|
||||
}
|
||||
|
||||
/// Spawn a process using the backend selected by the prepared sandbox request.
|
||||
pub async fn spawn_process(request: SpawnRequest<'_>) -> Result<SpawnedProcess> {
|
||||
if request.sandbox == SandboxType::WindowsRestrictedToken {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let windows = request
|
||||
.windows_sandbox
|
||||
.context("missing Windows sandbox spawn request")?;
|
||||
let codex_home = codex_utils_home_dir::find_codex_home()
|
||||
.context("windows sandbox: failed to resolve codex_home")?;
|
||||
let empty_paths = &[];
|
||||
let overrides = windows.filesystem_overrides;
|
||||
|
||||
return codex_windows_sandbox::spawn_windows_sandbox_session_for_level(
|
||||
codex_windows_sandbox::WindowsSandboxSessionRequest {
|
||||
permission_profile: windows.permission_profile,
|
||||
workspace_roots: windows.workspace_roots,
|
||||
codex_home: codex_home.as_path(),
|
||||
command: request.command.to_vec(),
|
||||
cwd: request.cwd,
|
||||
env_map: request.env.clone(),
|
||||
windows_sandbox_level: windows.windows_sandbox_level,
|
||||
proxy_enforced: windows.proxy_enforced,
|
||||
proxy_settings_mode: windows.proxy_settings_mode,
|
||||
timeout_ms: None,
|
||||
read_roots_override: overrides
|
||||
.and_then(|value| value.read_roots_override.as_deref()),
|
||||
read_roots_include_platform_defaults: overrides
|
||||
.is_some_and(|value| value.read_roots_include_platform_defaults),
|
||||
write_roots_override: overrides
|
||||
.and_then(|value| value.write_roots_override.as_deref()),
|
||||
deny_read_paths_override: overrides.map_or(empty_paths, |value| {
|
||||
value.additional_deny_read_paths.as_slice()
|
||||
}),
|
||||
deny_write_paths_override: overrides.map_or(empty_paths, |value| {
|
||||
value.additional_deny_write_paths.as_slice()
|
||||
}),
|
||||
tty: request.tty,
|
||||
stdin_open: request.stdin_open,
|
||||
use_private_desktop: windows.use_private_desktop,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
anyhow::bail!("Windows sandbox process spawn is unavailable on this platform");
|
||||
}
|
||||
|
||||
let (program, args) = request
|
||||
.command
|
||||
.split_first()
|
||||
.context("missing program for process spawn")?;
|
||||
if request.tty {
|
||||
codex_utils_pty::pty::spawn_process(
|
||||
program,
|
||||
args,
|
||||
request.cwd,
|
||||
request.env,
|
||||
request.arg0,
|
||||
TerminalSize::default(),
|
||||
request.inherited_fds,
|
||||
)
|
||||
.await
|
||||
} else if request.stdin_open {
|
||||
codex_utils_pty::pipe::spawn_process(
|
||||
program,
|
||||
args,
|
||||
request.cwd,
|
||||
request.env,
|
||||
request.arg0,
|
||||
request.inherited_fds,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
request.cwd,
|
||||
request.env,
|
||||
request.arg0,
|
||||
request.inherited_fds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -263,31 +263,31 @@ async fn spawn_process_with_stdin_mode(
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes (no PTY), returning handles for stdin, split output, and exit.
|
||||
/// Spawn a process using regular pipes and preserve selected inherited file
|
||||
/// descriptors across exec on Unix.
|
||||
pub async fn spawn_process(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
inherited_fds: &[i32],
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Piped, &[]).await
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes, but close stdin immediately.
|
||||
pub async fn spawn_process_no_stdin(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_no_stdin_with_inherited_fds(program, args, cwd, env, arg0, &[]).await
|
||||
spawn_process_with_stdin_mode(
|
||||
program,
|
||||
args,
|
||||
cwd,
|
||||
env,
|
||||
arg0,
|
||||
PipeStdinMode::Piped,
|
||||
inherited_fds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes, close stdin immediately, and preserve
|
||||
/// selected inherited file descriptors across exec on Unix.
|
||||
pub async fn spawn_process_no_stdin_with_inherited_fds(
|
||||
pub async fn spawn_process_no_stdin(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
|
||||
@@ -122,7 +122,8 @@ fn platform_native_pty_system() -> Box<dyn portable_pty::PtySystem + Send> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a process attached to a PTY, returning handles for stdin, split output, and exit.
|
||||
/// Spawn a process attached to a PTY, preserving selected inherited file
|
||||
/// descriptors across exec on Unix.
|
||||
pub async fn spawn_process(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
@@ -130,19 +131,6 @@ pub async fn spawn_process(
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
size: TerminalSize,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_inherited_fds(program, args, cwd, env, arg0, size, &[]).await
|
||||
}
|
||||
|
||||
/// Spawn a process attached to a PTY, preserving any inherited file
|
||||
/// descriptors listed in `inherited_fds` across exec on Unix.
|
||||
pub async fn spawn_process_with_inherited_fds(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
size: TerminalSize,
|
||||
inherited_fds: &[i32],
|
||||
) -> Result<SpawnedProcess> {
|
||||
if program.is_empty() {
|
||||
|
||||
@@ -7,10 +7,6 @@ use crate::ProcessDriver;
|
||||
use crate::SpawnedProcess;
|
||||
use crate::TerminalSize;
|
||||
use crate::combine_output_receivers;
|
||||
#[cfg(unix)]
|
||||
use crate::pipe::spawn_process_no_stdin_with_inherited_fds;
|
||||
#[cfg(unix)]
|
||||
use crate::pty::spawn_process_with_inherited_fds;
|
||||
use crate::spawn_from_driver;
|
||||
use crate::spawn_pipe_process;
|
||||
use crate::spawn_pipe_process_no_stdin;
|
||||
@@ -365,6 +361,7 @@ async fn pty_python_repl_emits_output_and_exits() -> anyhow::Result<()> {
|
||||
&env_map,
|
||||
&None,
|
||||
TerminalSize::default(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let (session, mut output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
@@ -421,7 +418,7 @@ async fn pipe_process_round_trips_stdin() -> anyhow::Result<()> {
|
||||
)
|
||||
};
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None).await?;
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None, &[]).await?;
|
||||
let (session, output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
let writer = session.writer_sender();
|
||||
let newline = if cfg!(windows) { "\r\n" } else { "\n" };
|
||||
@@ -454,7 +451,7 @@ async fn pipe_process_detaches_from_parent_session() -> anyhow::Result<()> {
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let script = "echo $$; sleep 0.2";
|
||||
let (program, args) = shell_command(script);
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None).await?;
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None, &[]).await?;
|
||||
|
||||
let (_session, mut output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
let pid_bytes =
|
||||
@@ -493,8 +490,15 @@ async fn pipe_and_pty_share_interface() -> anyhow::Result<()> {
|
||||
let (pipe_program, pipe_args) = shell_command(&echo_sleep_command("pipe_ok"));
|
||||
let (pty_program, pty_args) = shell_command(&echo_sleep_command("pty_ok"));
|
||||
|
||||
let pipe =
|
||||
spawn_pipe_process(&pipe_program, &pipe_args, Path::new("."), &env_map, &None).await?;
|
||||
let pipe = spawn_pipe_process(
|
||||
&pipe_program,
|
||||
&pipe_args,
|
||||
Path::new("."),
|
||||
&env_map,
|
||||
&None,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let pty = spawn_pty_process(
|
||||
&pty_program,
|
||||
&pty_args,
|
||||
@@ -502,6 +506,7 @@ async fn pipe_and_pty_share_interface() -> anyhow::Result<()> {
|
||||
&env_map,
|
||||
&None,
|
||||
TerminalSize::default(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let (_pipe_session, pipe_output_rx, pipe_exit_rx) = combine_spawned_output(pipe);
|
||||
@@ -537,7 +542,7 @@ async fn pipe_drains_stderr_without_stdout_activity() -> anyhow::Result<()> {
|
||||
let script = "import sys\nchunk = 'E' * 65536\nfor _ in range(64):\n sys.stderr.write(chunk)\n sys.stderr.flush()\n";
|
||||
let args = vec!["-c".to_string(), script.to_string()];
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let spawned = spawn_pipe_process(&python, &args, Path::new("."), &env_map, &None).await?;
|
||||
let spawned = spawn_pipe_process(&python, &args, Path::new("."), &env_map, &None, &[]).await?;
|
||||
let (_session, output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
|
||||
let (output, code) = collect_output_until_exit(output_rx, exit_rx, /*timeout_ms*/ 10_000).await;
|
||||
@@ -553,7 +558,7 @@ async fn pipe_process_can_expose_split_stdout_and_stderr() -> anyhow::Result<()>
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let (program, args) = shell_command(&split_stdout_stderr_command());
|
||||
let spawned =
|
||||
spawn_pipe_process_no_stdin(&program, &args, Path::new("."), &env_map, &None).await?;
|
||||
spawn_pipe_process_no_stdin(&program, &args, Path::new("."), &env_map, &None, &[]).await?;
|
||||
let SpawnedProcess {
|
||||
session: _session,
|
||||
stdout_rx,
|
||||
@@ -747,7 +752,7 @@ async fn pipe_terminate_aborts_detached_readers() -> anyhow::Result<()> {
|
||||
let script =
|
||||
"setsid sh -c 'i=0; while [ $i -lt 200 ]; do echo tick; sleep 0.01; i=$((i+1)); done' &";
|
||||
let (program, args) = shell_command(script);
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None).await?;
|
||||
let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None, &[]).await?;
|
||||
let (session, mut output_rx, _exit_rx) = combine_spawned_output(spawned);
|
||||
|
||||
let _ = tokio::time::timeout(tokio::time::Duration::from_millis(500), output_rx.recv())
|
||||
@@ -787,6 +792,7 @@ async fn pty_terminate_kills_background_children_in_same_process_group() -> anyh
|
||||
&env_map,
|
||||
&None,
|
||||
TerminalSize::default(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let (session, mut output_rx, _exit_rx) = combine_spawned_output(spawned);
|
||||
@@ -841,7 +847,7 @@ async fn pty_spawn_can_preserve_inherited_fds() -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
let script = "printf __preserved__ >\"/dev/fd/$PRESERVED_FD\"";
|
||||
let spawned = spawn_process_with_inherited_fds(
|
||||
let spawned = spawn_pty_process(
|
||||
"/bin/sh",
|
||||
&["-c".to_string(), script.to_string()],
|
||||
Path::new("."),
|
||||
@@ -893,7 +899,7 @@ async fn pty_preserving_inherited_fds_keeps_python_repl_running() -> anyhow::Res
|
||||
preserved_fd.as_raw_fd().to_string(),
|
||||
);
|
||||
|
||||
let spawned = spawn_process_with_inherited_fds(
|
||||
let spawned = spawn_pty_process(
|
||||
&python,
|
||||
&[],
|
||||
Path::new("."),
|
||||
@@ -959,7 +965,7 @@ async fn pty_spawn_with_inherited_fds_reports_exec_failures() -> anyhow::Result<
|
||||
let write_end = unsafe { std::fs::File::from_raw_fd(fds[1]) };
|
||||
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let spawn_result = spawn_process_with_inherited_fds(
|
||||
let spawn_result = spawn_pty_process(
|
||||
"/definitely/missing/command",
|
||||
&[],
|
||||
Path::new("."),
|
||||
@@ -1008,7 +1014,7 @@ async fn pty_spawn_with_inherited_fds_supports_resize() -> anyhow::Result<()> {
|
||||
|
||||
let env_map: HashMap<String, String> = std::env::vars().collect();
|
||||
let script = "stty -echo; printf 'start:%s\\n' \"$(stty size)\"; IFS= read _line; printf 'after:%s\\n' \"$(stty size)\"";
|
||||
let spawned = spawn_process_with_inherited_fds(
|
||||
let spawned = spawn_pty_process(
|
||||
"/bin/sh",
|
||||
&["-c".to_string(), script.to_string()],
|
||||
Path::new("."),
|
||||
@@ -1079,7 +1085,7 @@ async fn pipe_spawn_no_stdin_can_preserve_inherited_fds() -> anyhow::Result<()>
|
||||
);
|
||||
|
||||
let script = "printf __pipe_preserved__ >\"/dev/fd/$PRESERVED_FD\"";
|
||||
let spawned = spawn_process_no_stdin_with_inherited_fds(
|
||||
let spawned = spawn_pipe_process_no_stdin(
|
||||
"/bin/sh",
|
||||
&["-c".to_string(), script.to_string()],
|
||||
Path::new("."),
|
||||
|
||||
@@ -74,6 +74,7 @@ async fn conpty_delivers_input_to_foreground_children() -> anyhow::Result<()> {
|
||||
&env,
|
||||
/*arg0*/ &None,
|
||||
TerminalSize::default(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let (session, mut output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
@@ -126,6 +127,7 @@ async fn conpty_ctrl_c_interrupts_powershell_foreground_child() -> anyhow::Resul
|
||||
&env,
|
||||
/*arg0*/ &None,
|
||||
TerminalSize::default(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let (session, mut output_rx, exit_rx) = combine_spawned_output(spawned);
|
||||
|
||||
Reference in New Issue
Block a user