mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
Add experimental Windows sandbox service provisioning (#42353)
## What changed - Add the default-off `windows_sandbox_service` feature and expose it through app-server experimental feature enablement. - When enabled, attempt service provisioning during elevated Windows sandbox setup, using the effective network proxy ports and listener roles. Continue through the existing setup path when the service is unavailable or the configuration is unsupported. - Report unsupported Codex home drives as service unavailability so the client can fall back to the elevated helper. ## Testing - Add a Windows-only unit test covering HTTP and SOCKS listener derivation from the effective proxy configuration. GitOrigin-RevId: 40b119cf51f84e5d0e94d6a0a8d5b506d1123526
This commit is contained in:
@@ -302,6 +302,7 @@ Example with notification opt-out:
|
||||
- `mcpServer/event/stream/stop` (experimental) — stop the caller's event subscription by `subscriptionId`.
|
||||
- `mcpServer/tool/call` — call a tool on a thread's configured MCP server by `threadId`, `server`, `tool`, optional `arguments`, and optional `_meta`, returning the MCP tool result. Parent-owned Multi-Agent V2 subagents reject direct tool calls.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
The default-off `windows_sandbox_service` feature enables attempting service provisioning for elevated setup. Clients can set it through `experimentalFeature/enablement/set` before starting setup; when disabled, setup uses the existing elevated helper directly.
|
||||
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id. With logs enabled, includes bounded recent failed Guardian review actions, decisions, and reviewer history from the reported thread and its descendants, linked to the reviewed turn and target item where available. Rollout selection preserves the reported thread and prioritizes children with retained failed reviews before newer children, including each selected thread's available Guardian trunk rollout. `feedback-thread-index.json` lists selected filenames and bounded omission details; it describes selection, not successful delivery. Failed-review captures are process-local, so missing evidence does not establish that no denial occurred.
|
||||
- `config/read` — fetch the runtime-effective config after resolving config layering and managed requirements, including opaque `desktop` values stored in `config.toml`. When configured, the `packagedDefaults` layer has the lowest precedence.
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome`, optional `cwds`, and an optional `migrationSource` selector. Omitted, `null`, or unrecognized migration-source values retain the default behavior. The deprecated optional `source` field remains accepted for compatibility but does not select the migration source. Each detected item includes `cwd` (`null` for home), and multi-item migrations may additionally include structured `details` with plugin ids, skill names, memory, session metadata, or other artifact names. The response also includes connector candidates inferred from detected source sessions, with a normalized display `name`, the number of detected sessions that used the connector, and the source metadata field used for detection.
|
||||
|
||||
@@ -71,6 +71,7 @@ const SUPPORTED_EXPERIMENTAL_FEATURE_ENABLEMENT: &[&str] = &[
|
||||
"remote_control",
|
||||
"remote_plugin",
|
||||
"tool_suggest",
|
||||
"windows_sandbox_service",
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -83,8 +83,82 @@ impl WindowsSandboxRequestProcessor {
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
};
|
||||
let setup_result =
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await;
|
||||
let setup_result = async {
|
||||
// Workload identity is process-local, so use the existing helper path with
|
||||
// the caller's resolved configuration instead of loading auth in the service.
|
||||
#[cfg(target_os = "windows")]
|
||||
if setup_mode == CoreWindowsSandboxSetupMode::Elevated
|
||||
&& config.features.enabled(Feature::WindowsSandboxService)
|
||||
&& !codex_login::is_workload_identity_selected()
|
||||
{
|
||||
let provisioning = match config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.map_or_else(
|
||||
|| {
|
||||
Ok((
|
||||
codex_windows_sandbox::WindowsSandboxProvisioningSettings::from_environment(
|
||||
&setup_request.permission_profile,
|
||||
&setup_request.env_map,
|
||||
),
|
||||
codex_windows_sandbox::WindowsSandboxProxyListeners::from_environment(
|
||||
&setup_request.permission_profile,
|
||||
&setup_request.env_map,
|
||||
),
|
||||
))
|
||||
},
|
||||
codex_core::config::NetworkProxySpec::windows_sandbox_proxy_listeners,
|
||||
)
|
||||
{
|
||||
Ok(provisioning) => Some(provisioning),
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Windows sandbox service does not support the configured proxy listeners; falling back to elevated setup: {error}"
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some((settings, listeners)) = provisioning {
|
||||
let service_setup_request = setup_request.clone();
|
||||
let service_setup_start = Instant::now();
|
||||
tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
|
||||
if codex_windows_sandbox::ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
|
||||
&service_setup_request.permission_profile,
|
||||
&service_setup_request.workspace_roots,
|
||||
).is_err() {
|
||||
// The existing setup path can still succeed for completed
|
||||
// provisioning without resolving the current profile.
|
||||
return Ok(());
|
||||
}
|
||||
// The shared setup path below handles helper fallback and
|
||||
// refreshes workspace ACLs after provisioning.
|
||||
codex_windows_sandbox::provision_windows_sandbox_via_service(
|
||||
&service_setup_request.codex_home,
|
||||
settings,
|
||||
listeners,
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"Windows sandbox service provisioning task failed: {error}"
|
||||
)
|
||||
})
|
||||
.and_then(std::convert::identity)
|
||||
.inspect_err(|error| {
|
||||
codex_core::windows_sandbox::emit_windows_sandbox_setup_failure_metrics(
|
||||
setup_mode,
|
||||
service_setup_start.elapsed(),
|
||||
error,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await
|
||||
}
|
||||
.await;
|
||||
let notification = WindowsSandboxSetupCompletedNotification {
|
||||
mode: match setup_mode {
|
||||
CoreWindowsSandboxSetupMode::Elevated => WindowsSandboxSetupMode::Elevated,
|
||||
|
||||
@@ -1041,6 +1041,9 @@
|
||||
"web_search_request": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"windows_sandbox_service": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"workspace_dependencies": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -6353,6 +6356,9 @@
|
||||
"web_search_request": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"windows_sandbox_service": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"workspace_dependencies": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -98,6 +98,46 @@ impl NetworkProxySpec {
|
||||
self.config.allow_local_binding
|
||||
}
|
||||
|
||||
/// Returns the firewall settings and listener identities used by the Windows provisioning service.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn windows_sandbox_proxy_listeners(
|
||||
&self,
|
||||
) -> std::io::Result<(
|
||||
codex_windows_sandbox::WindowsSandboxProvisioningSettings,
|
||||
codex_windows_sandbox::WindowsSandboxProxyListeners,
|
||||
)> {
|
||||
if !self.config.enabled {
|
||||
return Ok((
|
||||
codex_windows_sandbox::WindowsSandboxProvisioningSettings::default(),
|
||||
codex_windows_sandbox::WindowsSandboxProxyListeners::default(),
|
||||
));
|
||||
}
|
||||
|
||||
let proxy_ports = self.configured_proxy_ports()?;
|
||||
let http_port = self
|
||||
.proxy_host_and_port()
|
||||
.rsplit_once(':')
|
||||
.and_then(|(_, port)| port.parse::<u16>().ok())
|
||||
.ok_or_else(|| std::io::Error::other("invalid HTTP proxy listener port"))?;
|
||||
let socks_port = self.config.enable_socks5.then(|| {
|
||||
proxy_ports
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|port| *port != http_port)
|
||||
.unwrap_or(http_port)
|
||||
});
|
||||
Ok((
|
||||
codex_windows_sandbox::WindowsSandboxProvisioningSettings {
|
||||
proxy_ports,
|
||||
allow_local_binding: self.config.allow_local_binding,
|
||||
},
|
||||
codex_windows_sandbox::WindowsSandboxProxyListeners {
|
||||
http_ports: vec![http_port],
|
||||
socks_ports: socks_port.into_iter().collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn from_config_and_constraints(
|
||||
config: NetworkProxyConfig,
|
||||
requirements: Option<NetworkConstraints>,
|
||||
|
||||
@@ -44,6 +44,38 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() {
|
||||
assert_eq!(state.audit_metadata(), &metadata);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_sandbox_proxy_listeners_preserve_effective_protocol_roles() {
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig {
|
||||
enabled: true,
|
||||
proxy_url: "http://127.0.0.1:48081".to_string(),
|
||||
socks_url: "socks5h://127.0.0.1:3128".to_string(),
|
||||
allow_local_binding: true,
|
||||
..NetworkProxyConfig::default()
|
||||
},
|
||||
/*requirements*/ None,
|
||||
&PermissionProfile::workspace_write(),
|
||||
)
|
||||
.expect("effective network configuration should be valid");
|
||||
|
||||
assert_eq!(
|
||||
spec.windows_sandbox_proxy_listeners()
|
||||
.expect("effective proxy listeners should resolve"),
|
||||
(
|
||||
codex_windows_sandbox::WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128, 48081],
|
||||
allow_local_binding: true,
|
||||
},
|
||||
codex_windows_sandbox::WindowsSandboxProxyListeners {
|
||||
http_ports: vec![48081],
|
||||
socks_ports: vec![3128],
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn environment_policy_replaces_soft_controller_allowlist_and_preserves_denials() {
|
||||
let requirements = NetworkConstraints {
|
||||
|
||||
@@ -268,12 +268,7 @@ pub async fn run_windows_sandbox_setup(request: WindowsSandboxSetupRequest) -> a
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
emit_windows_sandbox_setup_failure_metrics(
|
||||
mode,
|
||||
originator_tag.as_str(),
|
||||
start.elapsed(),
|
||||
&err,
|
||||
);
|
||||
emit_windows_sandbox_setup_failure_metrics(mode, start.elapsed(), &err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
@@ -351,15 +346,17 @@ fn emit_windows_sandbox_setup_success_metrics(
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_windows_sandbox_setup_failure_metrics(
|
||||
/// Records setup failures, including service attempts that fail before the helper path.
|
||||
pub fn emit_windows_sandbox_setup_failure_metrics(
|
||||
mode: WindowsSandboxSetupMode,
|
||||
originator_tag: &str,
|
||||
duration: std::time::Duration,
|
||||
_err: &anyhow::Error,
|
||||
) {
|
||||
let Some(metrics) = codex_otel::global() else {
|
||||
return;
|
||||
};
|
||||
let originator_tag = sanitize_metric_tag_value(originator().value.as_str());
|
||||
let originator_tag = originator_tag.as_str();
|
||||
let mode_tag = windows_sandbox_setup_mode_tag(mode);
|
||||
let _ = metrics.record_duration(
|
||||
"codex.windows_sandbox.setup_duration_ms",
|
||||
|
||||
@@ -364,6 +364,8 @@ pub enum Feature {
|
||||
WindowsSandbox,
|
||||
/// Use the elevated Windows sandbox pipeline (setup + runner).
|
||||
WindowsSandboxElevated,
|
||||
/// Attempt elevated Windows sandbox provisioning through the installed service.
|
||||
WindowsSandboxService,
|
||||
/// Legacy remote models flag kept for backward compatibility.
|
||||
RemoteModels,
|
||||
/// Removed legacy git commit attribution guidance flag.
|
||||
@@ -1171,6 +1173,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WindowsSandboxService,
|
||||
key: "windows_sandbox_service",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RemoteModels,
|
||||
key: "remote_models",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Authenticated local IPC for the Windows sandbox provisioning service.
|
||||
//! Configuration parse failures defer provisioning to the client's elevated helper.
|
||||
//! Configuration parse failures and unsupported home drives defer provisioning to
|
||||
//! the client's elevated helper.
|
||||
//! Shutdown wakeups are retried until the listener connects or stops.
|
||||
|
||||
mod authentication;
|
||||
@@ -125,6 +126,9 @@ pub(crate) fn run(shutdown: Arc<AtomicBool>, on_ready: impl FnOnce() -> Result<(
|
||||
let result = handle_request(pipe.0, &authorized_process, &sandbox_sid, &shutdown);
|
||||
let response = match result {
|
||||
Ok(response) => response,
|
||||
Err(error) if error.is::<home::UnsupportedHomeDrive>() => {
|
||||
SandboxProvisioningResponse::Unavailable
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("sandbox provisioning request failed: {error}");
|
||||
let mut message = String::new();
|
||||
|
||||
@@ -21,6 +21,18 @@ use windows_sys::Win32::Storage::FileSystem as filesystem;
|
||||
|
||||
const DRIVE_FIXED: u32 = 3;
|
||||
|
||||
/// The service does not support this drive; the interactive helper may still work.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct UnsupportedHomeDrive;
|
||||
|
||||
impl std::fmt::Display for UnsupportedHomeDrive {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("Codex home must be located on a fixed local drive")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnsupportedHomeDrive {}
|
||||
|
||||
pub(super) struct OwnedHandle(pub(super) HANDLE);
|
||||
|
||||
impl Drop for OwnedHandle {
|
||||
@@ -41,7 +53,7 @@ pub(super) fn prepare_codex_home(requested: &Path) -> Result<(PathBuf, Vec<Owned
|
||||
if unsafe { filesystem::GetDriveTypeW(to_wide(requested_root.as_os_str()).as_ptr()) }
|
||||
!= DRIVE_FIXED
|
||||
{
|
||||
bail!("Codex home must be located on a fixed local drive");
|
||||
return Err(UnsupportedHomeDrive.into());
|
||||
}
|
||||
let parent = requested
|
||||
.parent()
|
||||
@@ -61,7 +73,7 @@ pub(super) fn prepare_codex_home(requested: &Path) -> Result<(PathBuf, Vec<Owned
|
||||
.last()
|
||||
.context("find the root of the requested Codex home")?;
|
||||
if unsafe { filesystem::GetDriveTypeW(to_wide(root.as_os_str()).as_ptr()) } != DRIVE_FIXED {
|
||||
bail!("Codex home must be located on a fixed local drive");
|
||||
return Err(UnsupportedHomeDrive.into());
|
||||
}
|
||||
if home != requested {
|
||||
pin_existing_ancestors(&home, &mut handles)?;
|
||||
|
||||
Reference in New Issue
Block a user