Files
codex/codex-rs/mxc-sandbox/src/lib.rs
T
iceweasel-oai 3df96b085c Filter uninspectable network roots from implicit MXC read grants (#54363)
## What changed

On Windows, omit network roots whose filesystem identity cannot be inspected when expanding implicit `:root` read access. Retain inspectable shares and roots containing the policy or command working directory, and apply the same filtering during policy inspection and command launch.

Keep explicit grants unchanged and continue expanding `:root` write and deny rules across all executor volumes.

## Testing

Extend policy tests to cover filtered read roots, explicit grant precedence, and approved-command write access across all volumes.

GitOrigin-RevId: c4d7c8ce2c9138cdb4518385e2cd05f66518628e
2026-10-15 17:36:47 +00:00

173 lines
5.3 KiB
Rust

//! Native Windows MXC helper. Policy conversion is portable; execution requires
//! a usable process security environment and never enters MXC's ACL fallbacks.
#[cfg(windows)]
mod native;
#[cfg(any(windows, test))]
mod policy;
mod transport;
#[cfg(windows)]
mod windows;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
use anyhow::ensure;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_windows_sandbox::environment_transport;
use serde::Deserialize;
use serde::Serialize;
pub const CODEX_WINDOWS_MXC_ARG1: &str = "--__codex-windows-mxc";
const CLIENT_ONLY_LOOPBACK_UNSUPPORTED: &str = "MXC cannot enforce managed networking with allow_local_binding=false: native host-loopback access is bidirectional";
fn validate_managed_network(network: &ManagedNetworkSandboxContext) -> Result<()> {
ensure!(
!network.loopback_ports.is_empty(),
"MXC managed networking requires dedicated proxy ports"
);
ensure!(
!network.loopback_ports.contains(&0),
"MXC proxy ports must be nonzero"
);
ensure!(
network.allow_local_binding,
CLIENT_ONLY_LOOPBACK_UNSUPPORTED
);
Ok(())
}
#[derive(Serialize, Deserialize)]
struct MxcCommand {
permissions: PermissionProfile,
sandbox_policy_cwd: PathBuf,
managed_network: Option<ManagedNetworkSandboxContext>,
command: Vec<String>,
#[serde(default)]
default_child_env: bool,
}
/// Inputs used to build an MXC helper invocation.
#[derive(Debug)]
pub struct CreateMxcCommandArgsParams<'a> {
pub command: Vec<String>,
pub permission_profile: &'a PermissionProfile,
pub sandbox_policy_cwd: &'a Path,
pub managed_network: Option<&'a ManagedNetworkSandboxContext>,
pub env: &'a mut HashMap<String, String>,
}
/// Wrap exact argv and add bounded launcher-only environment variables. The
/// helper removes these variables before starting the sandboxed command.
pub fn create_command_args(args: CreateMxcCommandArgsParams<'_>) -> Result<Vec<String>> {
let CreateMxcCommandArgsParams {
command,
permission_profile,
sandbox_policy_cwd,
managed_network,
env,
} = args;
sandbox_policy_cwd
.to_str()
.context("MXC requires a Unicode policy working directory")?;
if let Some(network) = managed_network {
validate_managed_network(network)?;
}
transport::encode(
&MxcCommand {
permissions: permission_profile.clone(),
sandbox_policy_cwd: sandbox_policy_cwd.to_owned(),
managed_network: managed_network.cloned(),
command,
default_child_env: env
.keys()
.all(|key| environment_transport::is_key(key.as_ref())),
},
env,
)?;
Ok(vec![CODEX_WINDOWS_MXC_ARG1.to_owned()])
}
/// Whether the executor can create a native MXC process security environment.
/// This deliberately excludes MXC's older AppContainer fallback backends.
pub fn is_available() -> bool {
#[cfg(windows)]
{
appcontainer_common::base_container_runner::BaseContainerRunner::is_process_security_environment_usable()
}
#[cfg(not(windows))]
{
false
}
}
#[cfg(windows)]
pub use codex_windows_sandbox::GLOB_SCAN_PROGRAM;
/// Resolve configured symbols and snapshot deny globs for policy inspection.
/// Reuses MXC's resolution without adding native grants or protections.
#[cfg(windows)]
pub fn prepare_file_system_policy(
policy: codex_protocol::permissions::FileSystemSandboxPolicy,
env: &HashMap<String, String>,
policy_cwd: &codex_utils_absolute_path::AbsolutePathBuf,
command_cwd: &Path,
) -> Result<codex_protocol::permissions::FileSystemSandboxPolicy> {
use codex_utils_path_uri::PathUri;
let env_entries = env
.iter()
.map(|(key, value)| format!("{key}={value}"))
.collect::<Vec<_>>();
let mut policy = policy::materialize_temporary_paths(policy, &env_entries)?;
let denied = codex_windows_sandbox::resolve_windows_deny_read_paths_in_environment(
&policy,
policy_cwd,
env,
command_cwd,
)
.map_err(anyhow::Error::msg)?;
policy = policy.with_expanded_deny_globs(denied);
if policy.has_full_disk_write_access() {
return Ok(policy);
}
let volumes = windows::volume_roots(policy_cwd.as_path(), command_cwd)?;
let read_volumes = windows::implicit_read_roots(&volumes, policy_cwd.as_path(), command_cwd)?;
let volumes = volumes
.into_iter()
.map(PathUri::from_host_native_path)
.collect::<std::io::Result<Vec<_>>>()?;
Ok(policy::materialize_volume_roots(
policy,
&volumes,
&read_volumes,
)?)
}
/// Entry point dispatched before ordinary Codex CLI parsing.
pub fn run_main() -> ! {
#[cfg(windows)]
{
match windows::run() {
Ok(exit_code) => std::process::exit(exit_code),
Err(error) => {
eprintln!("MXC launcher: {error:#}");
std::process::exit(1);
}
}
}
#[cfg(not(windows))]
{
eprintln!("MXC sandbox execution requires Windows");
std::process::exit(1);
}
}
#[cfg(test)]
#[path = "policy_tests.rs"]
mod tests;