Files
codex/codex-rs/sandboxing/src/launch.rs
T
viyatb-oai d2efdf1986 Route sandbox CLI launches through the shared sandbox manager (#60517)
## What changed

- Use `SandboxManager` to prepare `codex sandbox` commands, including permission profiles, workspace roots, and managed network proxy settings.
- Return a `WrappedSandboxCommand` that can produce an execution request or a configured process command, preserving native executable paths and Linux helper `argv[0]`. Require the session launcher for native Windows token sandboxes.
- Move macOS inherited-terminal policy handling into the shared manager, retaining Unix socket grants and the terminal input injection denial.
- Prepare managed network context alongside proxy environment variables and pass it through sandbox transformation.

## Testing

- Extend sandbox network proxy tests to macOS, covering blocked direct loopback access and explicitly allowed requests.
- Add coverage for Linux helper executable paths and dispatch names, including non-UTF-8 paths.

GitOrigin-RevId: 8b7396d21b2f34122699388f16047329a3f5e5c6
2026-11-10 04:01:23 +00:00

107 lines
3.7 KiB
Rust

//! Bind filesystem and network inputs for sandbox preparation on the execution host.
use crate::SandboxExecRequest;
use crate::SandboxType;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::process::Command;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemPath;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
/// A command prepared for its selected sandbox. Native Windows token sessions
/// must use the Windows session launcher.
#[must_use]
pub struct WrappedSandboxCommand {
pub(super) request: SandboxExecRequest,
// UTF-8 argv cannot represent every native executable path.
pub(super) native_program: Option<OsString>,
}
impl WrappedSandboxCommand {
/// Return the request with UTF-8 argv for callers that spawn the command themselves.
pub fn into_command_request(self) -> SandboxExecRequest {
self.request
}
/// Return a configured process command when argv carries all required sandbox enforcement.
pub fn into_direct_command(self) -> io::Result<Command> {
let request = self.request;
if request.sandbox == SandboxType::WindowsRestrictedToken {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"a native Windows sandbox requires its session launcher",
));
}
let (program, args) = request.command.split_first().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "sandbox command is empty")
})?;
let mut command = Command::new(
self.native_program
.as_deref()
.unwrap_or_else(|| OsStr::new(program)),
);
#[cfg(unix)]
if let Some(arg0) = request.arg0 {
use std::os::unix::process::CommandExt;
command.arg0(arg0);
}
command
.args(args)
.current_dir(request.cwd.to_abs_path()?)
.env_clear()
.envs(request.env);
Ok(command)
}
}
/// The authorized profile, resolved on the host that will enforce it.
#[derive(Debug)]
pub(super) struct PreparedLaunchPolicy {
pub(super) permissions: PermissionProfile,
}
impl PreparedLaunchPolicy {
/// An empty workspace list stays empty, even when the policy contains `:workspace_roots`.
pub(super) fn resolve(
permissions: PermissionProfile,
policy_cwd: &PathUri,
roots: Option<&[PathUri]>,
) -> io::Result<Self> {
let Some(roots) = roots else {
return Ok(Self { permissions });
};
native_path(policy_cwd, "sandbox policy cwd")?;
let workspace_roots = native_workspace_roots(roots)?;
let (file_system, network) = permissions.to_runtime_permissions();
for entry in &file_system.entries {
if let FileSystemPath::Path { path } = &entry.path {
native_path(path, "sandbox permission path")?;
}
}
let file_system =
file_system.materialize_project_roots_with_workspace_roots(&workspace_roots);
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
permissions.enforcement(),
&file_system,
network,
);
Ok(Self { permissions })
}
}
pub(super) fn native_workspace_roots(roots: &[PathUri]) -> io::Result<Vec<AbsolutePathBuf>> {
roots
.iter()
.map(|root| native_path(root, "sandbox workspace root"))
.collect::<io::Result<Vec<_>>>()
}
fn native_path(path: &PathUri, field: &str) -> io::Result<AbsolutePathBuf> {
path.to_abs_path()
.map_err(|err| io::Error::new(err.kind(), format!("invalid {field}: {err}")))
}