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:
iceweasel-oai
2026-07-20 22:35:14 +00:00
committed by copyberry
parent 60272096bc
commit 35c2278dd5
16 changed files with 494 additions and 254 deletions
+14 -14
View File
@@ -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,
+2 -14
View File
@@ -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() {
+22 -16
View File
@@ -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("."),
+2
View File
@@ -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);