mirror of
https://github.com/openai/codex.git
synced 2026-11-04 08:25:29 +00:00
Require the elevated Windows sandbox for network proxies (#32857)
## Why Network proxy enforcement on Windows requires the elevated sandbox backend. Silently selecting that backend for an unelevated configuration makes the effective sandbox differ from the configured mode. ## What changed - Reject enabled network proxy configurations on Windows unless managed requirements allow only the elevated sandbox and `windows.sandbox` resolves to `"elevated"`. - Preserve the configured sandbox backend during execution and validate proxy compatibility at config loading, sandbox setup, and process launch boundaries. - Defer this validation during cloud-config bootstrap so authoritative managed requirements can be loaded first. - Do not start or expose disabled Windows proxies, and render transitions to disabled networking as `<network enabled="false"></network>`. ## Testing - Cover Windows requirement and sandbox compatibility matrices, bootstrap behavior, backend selection, disabled proxy handling, and environment-context updates. GitOrigin-RevId: bfa1dda98594e0db61883f8e7f65bd560e3453e6
This commit is contained in:
1 parent
87f3e39fdf
commit
abbb8c569c
23 files changed
+496
-58
No files matched your search
@@ -99,6 +99,92 @@ use serde::Deserialize;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn load_config_with_elevated_only_windows_sandbox_requirement(
|
||||
cfg: ConfigToml,
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: AbsolutePathBuf,
|
||||
) -> std::io::Result<Config> {
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
windows: Some(codex_config::WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(vec![WindowsSandboxModeToml::Elevated]),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let mut requirements_with_sources = codex_config::ConfigRequirementsWithSources::default();
|
||||
requirements_with_sources.merge_unset_fields(
|
||||
codex_config::RequirementSource::Unknown,
|
||||
requirements_toml.clone(),
|
||||
);
|
||||
let requirements = ConfigRequirements::try_from(requirements_with_sources)?;
|
||||
let config_layer_stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)?;
|
||||
Config::load_config_with_layer_stack(
|
||||
codex_exec_server::LOCAL_FS.as_ref(),
|
||||
cfg,
|
||||
overrides,
|
||||
codex_home,
|
||||
config_layer_stack,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_network_proxy_validation() {
|
||||
let elevated_only = ConfigRequirementsToml {
|
||||
windows: Some(codex_config::WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(vec![WindowsSandboxModeToml::Elevated]),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
for (is_windows, requirements, network_proxy_enabled, allowed) in [
|
||||
(true, ConfigRequirementsToml::default(), true, false),
|
||||
(true, ConfigRequirementsToml::default(), false, true),
|
||||
(true, elevated_only, true, true),
|
||||
(
|
||||
true,
|
||||
ConfigRequirementsToml {
|
||||
windows: Some(codex_config::WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(vec![
|
||||
WindowsSandboxModeToml::Elevated,
|
||||
WindowsSandboxModeToml::Unelevated,
|
||||
]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(false, ConfigRequirementsToml::default(), true, true),
|
||||
] {
|
||||
assert_eq!(
|
||||
validate_windows_network_proxy_requirements_for_platform(
|
||||
is_windows,
|
||||
&requirements,
|
||||
network_proxy_enabled,
|
||||
)
|
||||
.is_ok(),
|
||||
allowed
|
||||
);
|
||||
}
|
||||
|
||||
for (is_windows, sandbox_level, network_proxy_enabled, allowed) in [
|
||||
(true, WindowsSandboxLevel::Disabled, true, false),
|
||||
(true, WindowsSandboxLevel::RestrictedToken, true, false),
|
||||
(true, WindowsSandboxLevel::Elevated, true, true),
|
||||
(true, WindowsSandboxLevel::RestrictedToken, false, true),
|
||||
(false, WindowsSandboxLevel::RestrictedToken, true, true),
|
||||
] {
|
||||
assert_eq!(
|
||||
validate_windows_sandbox_network_proxy_compatibility_for_platform(
|
||||
is_windows,
|
||||
sandbox_level,
|
||||
network_proxy_enabled,
|
||||
)
|
||||
.is_ok(),
|
||||
allowed
|
||||
);
|
||||
}
|
||||
}
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::TempDirExt;
|
||||
@@ -1493,7 +1579,7 @@ async fn network_proxy_feature_matrix_preserves_sandbox_network_semantics() -> s
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
let config = load_config_with_elevated_only_windows_sandbox_requirement(
|
||||
base_config,
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
@@ -1538,6 +1624,13 @@ sandbox = "elevated"
|
||||
)?;
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.cloud_config_bundle(
|
||||
CloudConfigBundleFixture::loader_with_enterprise_requirement(
|
||||
r#"[windows]
|
||||
allowed_sandbox_implementations = ["elevated"]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
.cli_overrides(vec![
|
||||
(
|
||||
"features.network_proxy.enabled".to_string(),
|
||||
@@ -1670,6 +1763,9 @@ async fn experimental_network_requirements_enable_proxy_without_feature() -> std
|
||||
r#"
|
||||
[experimental_network]
|
||||
enabled = true
|
||||
|
||||
[windows]
|
||||
allowed_sandbox_implementations = ["elevated"]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
@@ -1693,7 +1789,7 @@ enabled = true
|
||||
async fn network_proxy_feature_uses_profile_network_proxy_settings() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
let config = load_config_with_elevated_only_windows_sandbox_requirement(
|
||||
ConfigToml {
|
||||
features: Some(toml::from_str("network_proxy = true").expect("valid features")),
|
||||
default_permissions: Some("dev".to_string()),
|
||||
|
||||
@@ -601,6 +601,56 @@ fn build_network_proxy_spec(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_windows_sandbox_network_proxy_compatibility(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
network_proxy_configured: bool,
|
||||
) -> std::io::Result<()> {
|
||||
validate_windows_sandbox_network_proxy_compatibility_for_platform(
|
||||
cfg!(target_os = "windows"),
|
||||
windows_sandbox_level,
|
||||
network_proxy_configured,
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_windows_sandbox_network_proxy_compatibility_for_platform(
|
||||
is_windows: bool,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
network_proxy_configured: bool,
|
||||
) -> std::io::Result<()> {
|
||||
if is_windows
|
||||
&& network_proxy_configured
|
||||
&& windows_sandbox_level != WindowsSandboxLevel::Elevated
|
||||
{
|
||||
return Err(ConstraintError::NetworkProxyIncompatibleWithUnelevatedWindowsSandbox.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_windows_network_proxy_requirements(
|
||||
requirements: &ConfigRequirementsToml,
|
||||
network_proxy_configured: bool,
|
||||
) -> std::io::Result<()> {
|
||||
validate_windows_network_proxy_requirements_for_platform(
|
||||
cfg!(target_os = "windows"),
|
||||
requirements,
|
||||
network_proxy_configured,
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_windows_network_proxy_requirements_for_platform(
|
||||
is_windows: bool,
|
||||
requirements: &ConfigRequirementsToml,
|
||||
network_proxy_configured: bool,
|
||||
) -> std::io::Result<()> {
|
||||
if is_windows
|
||||
&& network_proxy_configured
|
||||
&& !requirements.allows_only_elevated_windows_sandbox()
|
||||
{
|
||||
return Err(ConstraintError::NetworkProxyRequiresElevatedWindowsSandboxRequirement.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configured thread persistence backend.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum ThreadStoreConfig {
|
||||
@@ -1230,6 +1280,12 @@ impl AuthManagerConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ConfigBuildPhase {
|
||||
CloudConfigBootstrap,
|
||||
Authoritative,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ConfigBuilder {
|
||||
codex_home: Option<PathBuf>,
|
||||
@@ -1288,10 +1344,15 @@ impl ConfigBuilder {
|
||||
|
||||
pub async fn build(self) -> std::io::Result<Config> {
|
||||
// Keep the large config-loading future off small runtime thread stacks.
|
||||
Box::pin(self.build_inner()).await
|
||||
Box::pin(self.build_inner(ConfigBuildPhase::Authoritative)).await
|
||||
}
|
||||
|
||||
async fn build_inner(self) -> std::io::Result<Config> {
|
||||
/// Builds the preliminary config needed to initialize cloud config loading.
|
||||
pub async fn build_for_cloud_config_bootstrap(self) -> std::io::Result<Config> {
|
||||
Box::pin(self.build_inner(ConfigBuildPhase::CloudConfigBootstrap)).await
|
||||
}
|
||||
|
||||
async fn build_inner(self, build_phase: ConfigBuildPhase) -> std::io::Result<Config> {
|
||||
let Self {
|
||||
codex_home,
|
||||
cli_overrides,
|
||||
@@ -1376,12 +1437,13 @@ impl ConfigBuilder {
|
||||
config_layer_stack.requirements().clone(),
|
||||
config_layer_stack.requirements_toml().clone(),
|
||||
)?;
|
||||
let mut config = Config::load_config_with_layer_stack(
|
||||
let mut config = Config::load_config_with_layer_stack_and_options(
|
||||
LOCAL_FS.as_ref(),
|
||||
lock_config_toml,
|
||||
harness_overrides,
|
||||
codex_home,
|
||||
lock_config_layer_stack,
|
||||
build_phase,
|
||||
)
|
||||
.await?;
|
||||
config.config_lock_toml = Some(Arc::new(expected_lock_config));
|
||||
@@ -1390,12 +1452,13 @@ impl ConfigBuilder {
|
||||
save_fields_resolved_from_model_catalog;
|
||||
return Ok(config);
|
||||
}
|
||||
Config::load_config_with_layer_stack(
|
||||
Config::load_config_with_layer_stack_and_options(
|
||||
LOCAL_FS.as_ref(),
|
||||
config_toml,
|
||||
harness_overrides,
|
||||
codex_home,
|
||||
config_layer_stack,
|
||||
build_phase,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -2967,6 +3030,25 @@ impl Config {
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: AbsolutePathBuf,
|
||||
config_layer_stack: ConfigLayerStack,
|
||||
) -> std::io::Result<Self> {
|
||||
Self::load_config_with_layer_stack_and_options(
|
||||
fs,
|
||||
cfg,
|
||||
overrides,
|
||||
codex_home,
|
||||
config_layer_stack,
|
||||
ConfigBuildPhase::Authoritative,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn load_config_with_layer_stack_and_options(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
cfg: ConfigToml,
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: AbsolutePathBuf,
|
||||
config_layer_stack: ConfigLayerStack,
|
||||
build_phase: ConfigBuildPhase,
|
||||
) -> std::io::Result<Self> {
|
||||
// Keep the large config-construction future off small test thread stacks.
|
||||
Box::pin(async move {
|
||||
@@ -3764,6 +3846,17 @@ impl Config {
|
||||
network_requirements,
|
||||
&network_permission_profile,
|
||||
)?;
|
||||
if matches!(build_phase, ConfigBuildPhase::Authoritative) {
|
||||
let network_proxy_enabled = network.as_ref().is_some_and(NetworkProxySpec::enabled);
|
||||
validate_windows_network_proxy_requirements(
|
||||
config_layer_stack.requirements_toml(),
|
||||
network_proxy_enabled,
|
||||
)?;
|
||||
validate_windows_sandbox_network_proxy_compatibility(
|
||||
windows_sandbox_level,
|
||||
network_proxy_enabled,
|
||||
)?;
|
||||
}
|
||||
let mut helper_readable_roots = get_readable_roots_required_for_codex_runtime(
|
||||
&codex_home,
|
||||
zsh_path.as_ref(),
|
||||
@@ -4157,11 +4250,21 @@ impl Config {
|
||||
NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
build_network_proxy_spec(
|
||||
let network = build_network_proxy_spec(
|
||||
configured_network_proxy_config,
|
||||
self.config_layer_stack.requirements().network.clone(),
|
||||
permission_profile,
|
||||
)
|
||||
)?;
|
||||
let network_proxy_enabled = network.as_ref().is_some_and(NetworkProxySpec::enabled);
|
||||
validate_windows_network_proxy_requirements(
|
||||
self.config_layer_stack.requirements_toml(),
|
||||
network_proxy_enabled,
|
||||
)?;
|
||||
validate_windows_sandbox_network_proxy_compatibility(
|
||||
WindowsSandboxLevel::from_config(self),
|
||||
network_proxy_enabled,
|
||||
)?;
|
||||
Ok(network)
|
||||
}
|
||||
|
||||
pub fn bundled_skills_enabled(&self) -> bool {
|
||||
|
||||
@@ -73,7 +73,7 @@ impl ConfigReloader for StaticNetworkProxyReloader {
|
||||
}
|
||||
|
||||
impl NetworkProxySpec {
|
||||
pub(crate) fn enabled(&self) -> bool {
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.config.enabled
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user