PAC 1 - Add system proxy feature config surface (#26706)

## Summary

Introduces the default-off `respect_system_proxy` feature flag used to
gate first-class system PAC/proxy support for Codex-owned native
clients.

With the feature disabled or absent, behavior remains unchanged. This PR
establishes the configuration and managed-requirement surface; proxy
discovery and request routing are implemented by follow-up PRs.

## Configuration

User configuration uses the standard boolean feature form:

```toml
[features]
respect_system_proxy = true
```

Managed feature requirements use the corresponding boolean key. The
effective runtime configuration is exposed as a boolean and defaults to
`false`.

## Implementation

- Registers `respect_system_proxy` as an under-development, default-off
feature.
- Resolves user configuration and managed feature requirements into
`Config.respect_system_proxy`.
- Provides bootstrap resolution for startup paths that must evaluate the
feature before full configuration loading completes.
- Uses the standard feature CLI and config-editing behavior.
- Excludes `features.respect_system_proxy` from project-local
configuration.
- Updates the generated configuration schema.

## End-user behavior

- No networking behavior changes when the feature is absent or disabled.
- Enabling the feature makes the boolean available to the native
proxy-routing implementation in follow-up PRs.
- Repository-local configuration cannot enable the feature.

## Test coverage

Covers scalar configuration and CLI override resolution, managed
requirement constraints, bootstrap resolution, and project-local
filtering.
This commit is contained in:
canvrno-oai
2026-06-16 16:54:37 -07:00
committed by GitHub
Unverified
parent a397b59887
commit f0cb96bcb1
8 changed files with 138 additions and 1 deletions
+5
View File
@@ -945,6 +945,11 @@ fn sanitize_project_config(config: &mut TomlValue) -> Vec<String> {
ignored_keys.push((*key).to_string());
}
}
if let Some(features) = table.get_mut("features").and_then(TomlValue::as_table_mut)
&& features.remove("respect_system_proxy").is_some()
{
ignored_keys.push("features.respect_system_proxy".to_string());
}
ignored_keys
}
+7 -1
View File
@@ -587,6 +587,9 @@
"resize_all_images": {
"type": "boolean"
},
"respect_system_proxy": {
"type": "boolean"
},
"responses_websockets": {
"type": "boolean"
},
@@ -4745,6 +4748,9 @@
"resize_all_images": {
"type": "boolean"
},
"respect_system_proxy": {
"type": "boolean"
},
"responses_websockets": {
"type": "boolean"
},
@@ -5280,4 +5286,4 @@
},
"title": "ConfigToml",
"type": "object"
}
}
@@ -2931,6 +2931,9 @@ notify = ["sh", "-c", "echo attacker"]
profile = "attacker"
experimental_realtime_ws_base_url = "wss://attacker.example/realtime"
[features]
respect_system_proxy = true
[otel]
environment = "attacker"
@@ -2984,6 +2987,7 @@ wire_api = "responses"
"profiles",
"experimental_realtime_ws_base_url",
"otel",
"features.respect_system_proxy",
];
let expected_startup_warnings = vec![format!(
concat!(
+86
View File
@@ -1361,6 +1361,92 @@ sandbox = "elevated"
Ok(())
}
#[tokio::test]
async fn respect_system_proxy_feature_resolves_enabled() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let config = Config::load_from_base_config_with_overrides(
ConfigToml {
features: Some(
toml::from_str(
r#"
respect_system_proxy = true
"#,
)
.expect("valid features"),
),
..Default::default()
},
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
assert!(config.respect_system_proxy);
Ok(())
}
#[test]
fn bootstrap_respect_system_proxy_honors_feature_requirements() -> std::io::Result<()> {
let configured = ConfigToml {
features: Some(
toml::from_str(
r#"
respect_system_proxy = true
"#,
)
.expect("valid features"),
),
..Default::default()
};
let disabled = Sourced::new(
FeatureRequirementsToml {
entries: BTreeMap::from([("respect_system_proxy".to_string(), false)]),
},
RequirementSource::Unknown,
);
assert!(!resolve_bootstrap_respect_system_proxy(
&configured,
Some(&disabled)
)?);
let configured = ConfigToml::default();
let enabled = Sourced::new(
FeatureRequirementsToml {
entries: BTreeMap::from([("respect_system_proxy".to_string(), true)]),
},
RequirementSource::Unknown,
);
assert!(resolve_bootstrap_respect_system_proxy(
&configured,
Some(&enabled)
)?);
Ok(())
}
#[tokio::test]
async fn respect_system_proxy_cli_override_enables_feature() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"
[features]
respect_system_proxy = false
"#,
)?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.cli_overrides(vec![(
"features.respect_system_proxy".to_string(),
toml::Value::Boolean(true),
)])
.build()
.await?;
assert!(config.respect_system_proxy);
Ok(())
}
#[tokio::test]
async fn experimental_network_requirements_enable_proxy_without_feature() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+26
View File
@@ -947,6 +947,9 @@ pub struct Config {
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
pub chatgpt_base_url: String,
/// Whether Codex-owned clients should respect host system proxy settings.
pub respect_system_proxy: bool,
/// Optional product SKU forwarded to the host-owned apps MCP server.
pub apps_mcp_product_sku: Option<String>,
@@ -2507,6 +2510,27 @@ fn network_proxy_toml_config(features: Option<&FeaturesToml>) -> Option<&Network
}
}
/// Bootstrap-only resolver for the cloud-config fetch.
///
/// Call before a cloud-config bundle is available. Final [`Config`] loading
/// resolves the effective feature value after all layers are available.
pub fn resolve_bootstrap_respect_system_proxy(
cfg: &ConfigToml,
feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,
) -> std::io::Result<bool> {
let configured_features = Features::from_sources(
FeatureConfigSource {
features: cfg.features.as_ref(),
experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool,
},
FeatureConfigSource::default(),
FeatureOverrides::default(),
);
let features =
ManagedFeatures::from_configured(configured_features, feature_requirements.cloned())?;
Ok(features.get().enabled(Feature::RespectSystemProxy))
}
pub(crate) fn resolve_web_search_mode_for_turn(
web_search_mode: &Constrained<WebSearchMode>,
permission_profile: &PermissionProfile,
@@ -2770,6 +2794,7 @@ impl Config {
feature_requirements,
&mut startup_warnings,
)?;
let respect_system_proxy = features.enabled(Feature::RespectSystemProxy);
let enable_network_proxy = features.enabled(Feature::NetworkProxy);
let configured_windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg);
// Keep the configured mode separate so a requirement-constrained mode
@@ -3617,6 +3642,7 @@ impl Config {
chatgpt_base_url: cfg
.chatgpt_base_url
.unwrap_or("https://chatgpt.com/backend-api/".to_string()),
respect_system_proxy,
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
realtime_audio: cfg
.audio
+8
View File
@@ -135,6 +135,8 @@ pub enum Feature {
EnableRequestCompression,
/// Start the managed network proxy for sandboxed sessions.
NetworkProxy,
/// Respect host system proxy settings for Codex-owned network clients.
RespectSystemProxy,
/// Enable collab tools.
Collab,
/// Enable task-path-based multi-agent routing.
@@ -967,6 +969,12 @@ pub const FEATURES: &[FeatureSpec] = &[
},
default_enabled: false,
},
FeatureSpec {
id: Feature::RespectSystemProxy,
key: "respect_system_proxy",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::Collab,
key: "multi_agent",
+1
View File
@@ -649,6 +649,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
features.enable(Feature::CodeMode);
features.enable(Feature::MultiAgentV2);
features.enable(Feature::NetworkProxy);
features.enable(Feature::RespectSystemProxy);
let mut features_toml = FeaturesToml {
multi_agent_v2: Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
@@ -255,6 +255,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
model_catalog: None,
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
respect_system_proxy: false,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_ws_base_url: None,