mirror of
https://github.com/openai/codex.git
synced 2026-09-28 08:43:01 +08:00
Enable MXC TTY launches and managed networking in the exec server (#45524)
## What changed - Report `windows_mxc` from native MXC availability on Windows. - Allow MXC TTY launches and managed networking, using dedicated proxy listeners without requiring a shared-ingress restricting SID. - Reject MXC custom `argv0` and private-desktop launches, and continue failing closed when native MXC is unavailable. ## Testing Extend the Windows remote sandbox process-write test to cover MXC with both pipes and ConPTY. Retain coverage for rejecting MXC requests when native support is unavailable. GitOrigin-RevId: 80c5f319b9066d06b26f0a7eb7119a109e6ebef7
This commit is contained in:
Generated
+1
@@ -3298,6 +3298,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"codex-file-system",
|
||||
"codex-mxc-sandbox",
|
||||
"codex-network-proxy",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
|
||||
@@ -22,5 +22,8 @@ codex-utils-path-uri = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["arbitrary_precision", "raw_value"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
codex-mxc-sandbox = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -217,6 +217,10 @@ impl EnvironmentInfo {
|
||||
|
||||
/// Returns information about the current local exec-server process.
|
||||
pub fn local() -> Self {
|
||||
#[cfg(windows)]
|
||||
let windows_mxc = codex_mxc_sandbox::is_available();
|
||||
#[cfg(not(windows))]
|
||||
let windows_mxc = false;
|
||||
let cwd = std::env::current_dir().ok();
|
||||
let temporary_directories = Self::local_temporary_directories_with_cwd(cwd.as_deref());
|
||||
let normalize_temp_path = |path: std::ffi::OsString| {
|
||||
@@ -246,7 +250,7 @@ impl EnvironmentInfo {
|
||||
http_header_env_vars: true,
|
||||
sandboxed_file_streaming: true,
|
||||
shell_snapshot_v2: cfg!(unix),
|
||||
windows_mxc: false,
|
||||
windows_mxc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::process_telemetry::ProcessTelemetry;
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::ManagedProxyRouting;
|
||||
use codex_network_proxy::NetworkPolicyAuditObserver;
|
||||
use codex_network_proxy::NetworkPolicyDecider;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
@@ -14,6 +15,7 @@ use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_network_proxy::is_managed_mitm_ca_trust_bundle_path;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_network_proxy::strip_managed_proxy_env;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
use codex_sandboxing::SandboxDirectSpawnTransformRequest;
|
||||
@@ -87,17 +89,9 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
if let Some(sandbox) = params.sandbox.as_ref()
|
||||
&& sandbox.windows_sandbox_level == codex_protocol::config_types::WindowsSandboxLevel::Mxc
|
||||
{
|
||||
if params.tty || params.arg0.is_some() {
|
||||
if params.arg0.is_some() || sandbox.windows_sandbox_private_desktop {
|
||||
return Err(invalid_params(
|
||||
"MXC currently supports ordinary pipe launches only".to_owned(),
|
||||
));
|
||||
}
|
||||
if params.enforce_managed_network
|
||||
|| params.managed_network.is_some()
|
||||
|| params.network_proxy.is_some()
|
||||
{
|
||||
return Err(invalid_params(
|
||||
"MXC managed networking is not supported yet".to_owned(),
|
||||
"MXC custom argv0 and private-desktop launches are not supported".to_owned(),
|
||||
));
|
||||
}
|
||||
if !codex_sandboxing::windows_mxc_available() {
|
||||
@@ -126,6 +120,15 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
prepare_managed_network(
|
||||
params.managed_network.as_ref(),
|
||||
network_proxy,
|
||||
if params
|
||||
.sandbox
|
||||
.as_ref()
|
||||
.is_some_and(|sandbox| sandbox.windows_sandbox_level == WindowsSandboxLevel::Mxc)
|
||||
{
|
||||
ManagedProxyRouting::DedicatedListeners
|
||||
} else {
|
||||
ManagedProxyRouting::SharedIngress
|
||||
},
|
||||
env,
|
||||
network_policy_decider,
|
||||
network_policy_audit_observer,
|
||||
@@ -328,6 +331,7 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
async fn prepare_managed_network(
|
||||
managed_network: Option<&ManagedNetworkSandboxContext>,
|
||||
network_proxy: Option<&RemoteNetworkProxyLaunchConfig>,
|
||||
routing: ManagedProxyRouting,
|
||||
env: HashMap<String, String>,
|
||||
network_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
|
||||
@@ -363,7 +367,9 @@ async fn prepare_managed_network(
|
||||
registration_id: registration.executor_registration_id.clone(),
|
||||
}),
|
||||
});
|
||||
let mut builder = NetworkProxy::builder().state(Arc::new(state));
|
||||
let mut builder = NetworkProxy::builder()
|
||||
.state(Arc::new(state))
|
||||
.managed_proxy_routing(routing);
|
||||
if let Some(network_policy_decider) = network_policy_decider {
|
||||
builder = builder.policy_decider_arc(network_policy_decider);
|
||||
}
|
||||
@@ -376,15 +382,19 @@ async fn prepare_managed_network(
|
||||
.await
|
||||
.map_err(|err| internal_error(format!("failed to start executor network proxy: {err}")))?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let network_proxy_restricting_sid = Some(
|
||||
proxy
|
||||
.network_proxy_restricting_sid(/*environment_id*/ None)
|
||||
.ok_or_else(|| {
|
||||
internal_error(
|
||||
"managed Windows proxy route is missing its restricting SID".to_string(),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
let network_proxy_restricting_sid = if routing == ManagedProxyRouting::SharedIngress {
|
||||
Some(
|
||||
proxy
|
||||
.network_proxy_restricting_sid(/*environment_id*/ None)
|
||||
.ok_or_else(|| {
|
||||
internal_error(
|
||||
"managed Windows proxy route is missing its restricting SID".to_string(),
|
||||
)
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let network_proxy_restricting_sid = None;
|
||||
let prepared = proxy
|
||||
|
||||
@@ -133,29 +133,17 @@ async fn sandbox_request_wraps_native_argv_on_executor() {
|
||||
let mut params = params;
|
||||
params.sandbox.as_mut().unwrap().windows_sandbox_level =
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Mxc;
|
||||
for (tty, managed, message) in [
|
||||
(false, false, "native MXC is unavailable on this executor"),
|
||||
(
|
||||
true,
|
||||
false,
|
||||
"MXC currently supports ordinary pipe launches only",
|
||||
),
|
||||
(false, true, "MXC managed networking is not supported yet"),
|
||||
] {
|
||||
params.tty = tty;
|
||||
params.enforce_managed_network = managed;
|
||||
let error = prepare_exec_request(
|
||||
¶ms,
|
||||
HashMap::new(),
|
||||
Some(&runtime_paths),
|
||||
/*network_policy_decider*/ None,
|
||||
/*network_policy_audit_observer*/ None,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.expect("unsupported MXC must fail closed");
|
||||
assert_eq!(error.message, message);
|
||||
}
|
||||
let error = prepare_exec_request(
|
||||
¶ms,
|
||||
HashMap::new(),
|
||||
Some(&runtime_paths),
|
||||
/*network_policy_decider*/ None,
|
||||
/*network_policy_audit_observer*/ None,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.expect("unsupported MXC must fail closed");
|
||||
assert_eq!(error.message, "native MXC is unavailable on this executor");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -187,9 +187,10 @@ async fn accepted_websocket_interoperates_and_recovers_with_real_direct_executor
|
||||
.default_environment()
|
||||
.context("direct executor environment should be installed")?;
|
||||
|
||||
let expected_info = EnvironmentInfo::local();
|
||||
assert_eq!(
|
||||
timeout(TEST_TIMEOUT, environment.force_info()).await??,
|
||||
EnvironmentInfo::local()
|
||||
expected_info
|
||||
);
|
||||
let files = tempfile::tempdir()?;
|
||||
let large_file_path = files.path().join("large-response.bin");
|
||||
@@ -340,7 +341,7 @@ async fn accepted_websocket_interoperates_and_recovers_with_real_direct_executor
|
||||
));
|
||||
assert_eq!(
|
||||
timeout(TEST_TIMEOUT, environment.force_info()).await??,
|
||||
EnvironmentInfo::local()
|
||||
expected_info
|
||||
);
|
||||
|
||||
let recovered_read = timeout(Duration::from_secs(5), pending_read)
|
||||
|
||||
@@ -1297,6 +1297,7 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
|
||||
async fn assert_remote_windows_sandbox_process_write(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
expected_sandbox_type: codex_sandboxing::SandboxType,
|
||||
tty: bool,
|
||||
) -> Result<()> {
|
||||
if !selected_windows_sandbox_available(windows_sandbox_level) {
|
||||
eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host");
|
||||
@@ -1332,8 +1333,8 @@ async fn assert_remote_windows_sandbox_process_write(
|
||||
shell_snapshot: None,
|
||||
env_policy: /*env_policy*/ None,
|
||||
env: Default::default(),
|
||||
tty: false,
|
||||
pipe_stdin: true,
|
||||
tty,
|
||||
pipe_stdin: !tty,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
@@ -1347,7 +1348,8 @@ async fn assert_remote_windows_sandbox_process_write(
|
||||
};
|
||||
assert_eq!(session.sandbox_type, Some(expected_sandbox_type));
|
||||
|
||||
let write_response = session.process.write(b"hello\n".to_vec()).await?;
|
||||
let input = if tty { b"hello\r" } else { b"hello\n" };
|
||||
let write_response = session.process.write(input.to_vec()).await?;
|
||||
assert_eq!(write_response.status, WriteStatus::Accepted);
|
||||
let StartedExecProcess { process, .. } = session;
|
||||
let wake_rx = process.subscribe_wake();
|
||||
@@ -1766,13 +1768,21 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
|
||||
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
codex_sandboxing::SandboxType::WindowsRestrictedToken;
|
||||
codex_sandboxing::SandboxType::WindowsRestrictedToken,
|
||||
false;
|
||||
"restricted_token"
|
||||
)]
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::Mxc,
|
||||
codex_sandboxing::SandboxType::WindowsMxc;
|
||||
"mxc"
|
||||
codex_sandboxing::SandboxType::WindowsMxc,
|
||||
false;
|
||||
"mxc_pipe"
|
||||
)]
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::Mxc,
|
||||
codex_sandboxing::SandboxType::WindowsMxc,
|
||||
true;
|
||||
"mxc_conpty"
|
||||
)]
|
||||
#[cfg_attr(not(windows), ignore = "Windows-only exec-server sandbox process test")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -1780,8 +1790,10 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
|
||||
async fn remote_windows_sandbox_process_accepts_process_write(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
expected_sandbox_type: codex_sandboxing::SandboxType,
|
||||
tty: bool,
|
||||
) -> Result<()> {
|
||||
assert_remote_windows_sandbox_process_write(windows_sandbox_level, expected_sandbox_type).await
|
||||
assert_remote_windows_sandbox_process_write(windows_sandbox_level, expected_sandbox_type, tty)
|
||||
.await
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
|
||||
Reference in New Issue
Block a user