mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Route Windows sandbox proxy traffic by restricting SID (#34613)
## Why Elevated Windows sandboxes need stable managed-proxy ports while preserving the network policy and environment attribution of each sandboxed process. ## What changed - Keep shared HTTP and SOCKS5 loopback ingress listeners alive across managed-proxy instances. - Add a per-route restricting SID to elevated sandbox tokens and dispatch incoming connections to the matching proxy policy after attributing the client process. - Reject connections without exactly one registered route, remove routes when their proxy handle is dropped, and keep unsandboxed Windows launches off the managed ingress. - Provision the elevated sandbox with the configured proxy ports and local-binding setting, honoring the selected profile and CLI overrides. ## Testing - Add Windows unit tests for TCP ownership attribution, route selection, restricting-token propagation, and setup settings. - Add an end-to-end Windows test covering stable ports, isolated environment policies, HTTP and SOCKS5 routing, and route teardown. GitOrigin-RevId: 783fac6e0f904dc9bb1955b75d4a5895e8bb9690
This commit is contained in:
@@ -488,6 +488,7 @@ async fn run_command_under_windows_session(
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(config),
|
||||
proxy_settings_mode: WindowsSandboxProxySettingsMode::Reconcile,
|
||||
proxy_enforced: false,
|
||||
network_proxy_restricting_sid: None,
|
||||
timeout_ms: None,
|
||||
read_roots_override: None,
|
||||
read_roots_include_platform_defaults: false,
|
||||
|
||||
+47
-14
@@ -1445,6 +1445,14 @@ async fn cli_main(
|
||||
.await?;
|
||||
}
|
||||
Some(Subcommand::Sandbox(mut sandbox_cli)) => {
|
||||
let config_profile = sandbox_cli
|
||||
.config_profile
|
||||
.as_ref()
|
||||
.or(interactive.config_profile_v2.as_ref());
|
||||
prepend_config_flags(
|
||||
&mut sandbox_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(setup_cli) = sandbox_setup::parse_setup_command(&sandbox_cli.command)? {
|
||||
reject_remote_mode_for_subcommand(
|
||||
@@ -1452,7 +1460,11 @@ async fn cli_main(
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox setup",
|
||||
)?;
|
||||
sandbox_setup::run(setup_cli).await?;
|
||||
let cli_overrides = sandbox_cli
|
||||
.config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
sandbox_setup::run(setup_cli, config_profile.cloned(), cli_overrides).await?;
|
||||
return Ok(());
|
||||
}
|
||||
reject_remote_mode_for_subcommand(
|
||||
@@ -1460,15 +1472,7 @@ async fn cli_main(
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox",
|
||||
)?;
|
||||
let config_profile = sandbox_cli
|
||||
.config_profile
|
||||
.as_ref()
|
||||
.or(interactive.config_profile_v2.as_ref());
|
||||
let loader_overrides = loader_overrides_for_profile(config_profile)?;
|
||||
prepend_config_flags(
|
||||
&mut sandbox_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
#[cfg(target_os = "macos")]
|
||||
codex_cli::run_command_under_seatbelt(
|
||||
sandbox_cli,
|
||||
@@ -1879,16 +1883,29 @@ fn loader_overrides_for_profile(
|
||||
match profile_v2 {
|
||||
Some(profile_v2) => {
|
||||
let codex_home = find_codex_home()?;
|
||||
Ok(LoaderOverrides {
|
||||
user_config_path: Some(resolve_profile_v2_config_path(&codex_home, profile_v2)),
|
||||
user_config_profile: Some(profile_v2.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
Ok(loader_overrides_for_profile_at_codex_home(
|
||||
Some(profile_v2),
|
||||
&codex_home,
|
||||
))
|
||||
}
|
||||
None => Ok(LoaderOverrides::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn loader_overrides_for_profile_at_codex_home(
|
||||
profile_v2: Option<&ProfileV2Name>,
|
||||
codex_home: &std::path::Path,
|
||||
) -> LoaderOverrides {
|
||||
match profile_v2 {
|
||||
Some(profile_v2) => LoaderOverrides {
|
||||
user_config_path: Some(resolve_profile_v2_config_path(codex_home, profile_v2)),
|
||||
user_config_profile: Some(profile_v2.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
None => LoaderOverrides::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_print_under_development_feature_warning(codex_home: &std::path::Path, feature: &str) {
|
||||
let Some(spec) = FEATURES.iter().find(|spec| spec.key == feature) else {
|
||||
return;
|
||||
@@ -2706,6 +2723,22 @@ mod tests {
|
||||
Ok(profile_v2_for_subcommand(&cli.interactive, subcommand)?.map(ToString::to_string))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_loader_overrides_use_explicit_codex_home() -> anyhow::Result<()> {
|
||||
let codex_home = tempfile::tempdir()?;
|
||||
let profile: ProfileV2Name = "work".parse()?;
|
||||
|
||||
let overrides =
|
||||
loader_overrides_for_profile_at_codex_home(Some(&profile), codex_home.path());
|
||||
|
||||
assert_eq!(
|
||||
overrides.user_config_path,
|
||||
Some(resolve_profile_v2_config_path(codex_home.path(), &profile))
|
||||
);
|
||||
assert_eq!(overrides.user_config_profile, Some(profile));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_v2_is_rejected_for_config_management_subcommands() {
|
||||
assert!(profile_v2_for_args(&["codex", "--profile", "work", "features", "list"]).is_err());
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::ArgAction;
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_utils_cli::ProfileV2Name;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(group(
|
||||
@@ -54,9 +58,13 @@ impl SandboxSetupCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
||||
pub(crate) async fn run(
|
||||
cmd: SandboxSetupCommand,
|
||||
config_profile: Option<ProfileV2Name>,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
) -> anyhow::Result<()> {
|
||||
match cmd.setup_level()? {
|
||||
SandboxSetupLevel::Elevated => run_elevated(cmd).await,
|
||||
SandboxSetupLevel::Elevated => run_elevated(cmd, config_profile, cli_overrides).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +83,28 @@ pub(crate) fn parse_setup_command(
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn run_elevated(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
||||
async fn run_elevated(
|
||||
cmd: SandboxSetupCommand,
|
||||
config_profile: Option<ProfileV2Name>,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
) -> anyhow::Result<()> {
|
||||
let identity = resolve_sandbox_setup_identity(&cmd)?;
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(identity.codex_home.clone())
|
||||
.fallback_cwd(Some(identity.codex_home.clone()))
|
||||
.loader_overrides(super::loader_overrides_for_profile_at_codex_home(
|
||||
config_profile.as_ref(),
|
||||
&identity.codex_home,
|
||||
))
|
||||
.cli_overrides(cli_overrides)
|
||||
.build()
|
||||
.await
|
||||
.context("failed to load target user's Codex config for sandbox provisioning")?;
|
||||
|
||||
codex_core::windows_sandbox::run_elevated_provisioning_setup(
|
||||
identity.codex_home.as_path(),
|
||||
identity.real_user.as_str(),
|
||||
config.permissions.network.as_ref(),
|
||||
)?;
|
||||
ConfigEditsBuilder::new(identity.codex_home.as_path())
|
||||
.set_windows_sandbox_mode("elevated")
|
||||
|
||||
Reference in New Issue
Block a user