diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 2d2d04dd7f..bfceb074d4 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3992,6 +3992,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "codex-mxc-sandbox" +version = "0.0.0" +dependencies = [ + "anyhow", + "appcontainer_common", + "codex-protocol", + "learning_mode_windows", + "tracelogging", + "wxc_common", +] + [[package]] name = "codex-network-proxy" version = "0.0.0" @@ -4331,7 +4343,7 @@ name = "codex-sandboxing" version = "0.0.0" dependencies = [ "anyhow", - "appcontainer_common", + "codex-mxc-sandbox", "codex-network-proxy", "codex-otel", "codex-protocol", @@ -4347,7 +4359,6 @@ dependencies = [ "serde_json", "tempfile", "tokio", - "tracelogging", "tracing", "url", "which 8.0.0", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 3868eada9b..d964f5faac 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -83,6 +83,7 @@ members = [ "memories/read", "memories/write", "model-provider-info", + "mxc-sandbox", "models-manager", "network-proxy", "ollama", @@ -240,6 +241,7 @@ codex-mcp-extension = { path = "ext/mcp" } codex-mcp-server = { path = "mcp-server" } codex-model-provider-info = { path = "model-provider-info" } codex-models-manager = { path = "models-manager" } +codex-mxc-sandbox = { path = "mxc-sandbox" } codex-network-proxy = { path = "network-proxy" } codex-ollama = { path = "ollama" } codex-otel = { path = "otel" } @@ -302,6 +304,8 @@ core_test_support = { path = "core/tests/common" } mcp_test_support = { path = "mcp-server/tests/common" } # External +learning_mode_windows = { git = "https://github.com/microsoft/mxc", rev = "6cd3d58f05d3447e67109cfb75e042803b843ca4" } +wxc_common = { git = "https://github.com/microsoft/mxc", rev = "6cd3d58f05d3447e67109cfb75e042803b843ca4" } age = "0.11.1" ansi-to-tui = "8.0.1" anyhow = "1" diff --git a/codex-rs/mxc-sandbox/BUILD.bazel b/codex-rs/mxc-sandbox/BUILD.bazel new file mode 100644 index 0000000000..d9b18072e2 --- /dev/null +++ b/codex-rs/mxc-sandbox/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "mxc-sandbox", + crate_name = "codex_mxc_sandbox", +) diff --git a/codex-rs/mxc-sandbox/Cargo.toml b/codex-rs/mxc-sandbox/Cargo.toml new file mode 100644 index 0000000000..33bb833ee3 --- /dev/null +++ b/codex-rs/mxc-sandbox/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "codex-mxc-sandbox" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +test = false +doctest = false + +[lints] +workspace = true + +# This direct dependency pins MXC's transitive ETW dependency for Windows GNU linking. +[package.metadata.cargo-shear] +ignored = ["tracelogging"] + +[dependencies] +anyhow = { workspace = true } +codex-protocol = { workspace = true } +wxc_common = { workspace = true } + +[target.'cfg(windows)'.dependencies] +appcontainer_common = { workspace = true } +learning_mode_windows = { workspace = true } +# 1.2.4 imports OneCore_apiset, which the GNU Windows toolchain does not ship. +tracelogging = "=1.2.3" + +[dev-dependencies] diff --git a/codex-rs/mxc-sandbox/src/lib.rs b/codex-rs/mxc-sandbox/src/lib.rs new file mode 100644 index 0000000000..6072925828 --- /dev/null +++ b/codex-rs/mxc-sandbox/src/lib.rs @@ -0,0 +1,27 @@ +//! Native Windows process security environment availability and launch. + +#[cfg(windows)] +pub mod native; + +use codex_protocol::models::PermissionProfile; +use std::path::PathBuf; + +/// Typed inputs for the native policy adapter. +pub struct MxcCommand { + pub permissions: PermissionProfile, + pub sandbox_policy_cwd: PathBuf, + pub command: Vec, +} + +/// 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 + } +} diff --git a/codex-rs/mxc-sandbox/src/native.rs b/codex-rs/mxc-sandbox/src/native.rs new file mode 100644 index 0000000000..f3cc57a698 --- /dev/null +++ b/codex-rs/mxc-sandbox/src/native.rs @@ -0,0 +1,55 @@ +//! Run an already prepared MXC request with inherited stdio and job ownership. + +use anyhow::Context; +use anyhow::Result; +use anyhow::ensure; +use appcontainer_common::base_container_runner::BaseContainerRunner; +use learning_mode_windows::SecurityEnvironmentApi; +use wxc_common::logger::Logger; +use wxc_common::logger::Mode; +use wxc_common::models::ExecutionRequest; +use wxc_common::sandbox_process::SandboxBackend; +use wxc_common::sandbox_process::StdioMode; + +// The ETW functions used by tracelogging are documented Advapi32 exports on +// both Windows toolchains; 1.2.3 leaves their import library to the application. +#[link(name = "advapi32")] +unsafe extern "system" {} + +/// Launch a native-only request and wait for its exit status. +pub fn launch(request: &ExecutionRequest) -> Result { + ensure!( + !request + .policy + .capabilities + .iter() + .any(|capability| capability.eq_ignore_ascii_case("permissiveLearningMode")), + "MXC native launch does not support permissiveLearningMode" + ); + ensure!( + crate::is_available(), + "native MXC is unavailable on this Windows build" + ); + ensure!( + !request.policy.least_privilege_mode + && !request.policy.network_proxy.is_enabled() + && request.policy.capture_denials.is_none() + && !request.policy.fallback.allow_dacl_mutation, + "MXC native launch does not support fallback policies" + ); + // With no least-privilege mode, legacy proxy, or capture enabled, this + // probe guarantees BaseContainerRunner chooses PSEC rather than SBOX. + if !request.policy.denied_paths.is_empty() { + ensure!( + SecurityEnvironmentApi::load()?.supports_deny_paths()?, + "this Windows build cannot enforce native MXC deny paths" + ); + } + let mut logger = Logger::new(Mode::Buffer); + let mut child = BaseContainerRunner::new() + .spawn(request, &mut logger, StdioMode::Inherit) + .map_err(|error| anyhow::anyhow!("{}", error.error_message))?; + // Do not publish the SDK diagnostic buffer: it may contain command or + // environment data. This wrapper emits only the resulting launch error. + child.wait().context("waiting for the MXC command") +} diff --git a/codex-rs/sandboxing/Cargo.toml b/codex-rs/sandboxing/Cargo.toml index 654c3e5785..3cdd2e8c9b 100644 --- a/codex-rs/sandboxing/Cargo.toml +++ b/codex-rs/sandboxing/Cargo.toml @@ -12,10 +12,6 @@ doctest = false [lints] workspace = true -# This direct dependency pins MXC's transitive ETW dependency for Windows GNU linking. -[package.metadata.cargo-shear] -ignored = ["tracelogging"] - [dependencies] anyhow = { workspace = true } codex-network-proxy = { workspace = true } @@ -34,11 +30,9 @@ url = { workspace = true } which = { workspace = true } [target.'cfg(windows)'.dependencies] -appcontainer_common = { workspace = true } +codex-mxc-sandbox = { workspace = true } codex-otel = { workspace = true } codex-utils-home-dir = { workspace = true } -# 1.2.4 imports OneCore_apiset, which the GNU Windows toolchain does not ship. -tracelogging = "=1.2.3" [dev-dependencies] anyhow = { workspace = true } diff --git a/codex-rs/sandboxing/src/windows_mxc.rs b/codex-rs/sandboxing/src/windows_mxc.rs index bea672a580..97b32a2f0d 100644 --- a/codex-rs/sandboxing/src/windows_mxc.rs +++ b/codex-rs/sandboxing/src/windows_mxc.rs @@ -1,21 +1,15 @@ //! Native Windows MXC capability discovery and availability metrics. //! Probing is cached upstream; reporting happens at most once per process. -use appcontainer_common::base_container_runner::BaseContainerRunner; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; static AVAILABILITY_RECORDED: AtomicBool = AtomicBool::new(false); -// tracelogging 1.2.3 leaves its ETW import library to the application. These -// APIs are exported by Advapi32 in both the MSVC and GNU Windows toolchains. -#[link(name = "advapi32")] -unsafe extern "system" {} - pub(super) fn record_availability_once() { // Probe actual PSEC create/close support; symbol presence alone also // succeeds on transitional Windows builds where MXC is not enabled. - let available = BaseContainerRunner::is_process_security_environment_usable(); + let available = codex_mxc_sandbox::is_available(); if let Some(metrics) = codex_otel::global() && !AVAILABILITY_RECORDED.swap(true, Ordering::Relaxed) {