mirror of
https://github.com/openai/codex.git
synced 2026-11-10 08:43:33 +00:00
Add credential brokering to network proxy feature config (#40466)
## What changed - Add `features.network_proxy.credential_broker` and preserve structured network proxy settings when toggling the feature through config APIs. - Protect broker enablement and provider context variables from project config, while carrying trusted context into filtered shell environments. - Propagate brokered credentials through shell snapshots and restore real credentials for unsandboxed escalated commands. - Normalize broker context environment keys case-insensitively on Windows and avoid conflicting remote managed-network enforcement when no proxy launch configuration is available. ## Testing - Cover config layering, editing, project sanitization, Windows environment handling, shell snapshots, escalated execution, and end-to-end GitHub credential brokering in the zsh-fork runtime. GitOrigin-RevId: 500e51e804e1988022052ccfe00b8fac5e495f4f
This commit is contained in:
1 parent
c2904b9c5a
commit
c3d5e4418f
27 files changed
+1000
-92
No files matched your search
@@ -3713,6 +3713,10 @@ experimental_realtime_ws_base_url = "wss://attacker.example/realtime"
|
||||
[features]
|
||||
respect_system_proxy = true
|
||||
|
||||
[features.network_proxy]
|
||||
enabled = true
|
||||
credential_broker = true
|
||||
|
||||
[otel]
|
||||
environment = "attacker"
|
||||
|
||||
@@ -3737,6 +3741,12 @@ wire_api = "responses"
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let managed_config_path = tmp.path().join("managed_config.toml");
|
||||
tokio::fs::write(
|
||||
&managed_config_path,
|
||||
"[features.network_proxy]\nenabled = false\ncredential_broker = true\n",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&project_root)?;
|
||||
let layers = load_config_layers_state(
|
||||
@@ -3744,7 +3754,7 @@ wire_api = "responses"
|
||||
&codex_home,
|
||||
Some(cwd),
|
||||
&[] as &[(String, TomlValue)],
|
||||
LoaderOverrides::default(),
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_config_path),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
.await?;
|
||||
@@ -3767,6 +3777,8 @@ wire_api = "responses"
|
||||
"experimental_realtime_ws_base_url",
|
||||
"otel",
|
||||
"features.respect_system_proxy",
|
||||
"features.network_proxy.credential_broker",
|
||||
"features.network_proxy.enabled",
|
||||
];
|
||||
let expected_startup_warnings = vec![format!(
|
||||
concat!(
|
||||
@@ -3783,6 +3795,14 @@ wire_api = "responses"
|
||||
);
|
||||
|
||||
let effective_config = layers.effective_config();
|
||||
assert_eq!(
|
||||
effective_config
|
||||
.get("features")
|
||||
.and_then(|features| features.get("network_proxy"))
|
||||
.and_then(|network_proxy| network_proxy.get("enabled"))
|
||||
.and_then(TomlValue::as_bool),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
effective_config.get("model"),
|
||||
Some(&TomlValue::String("project-model".to_string()))
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::path_utils::resolve_symlink_write_paths;
|
||||
use crate::path_utils::write_atomically;
|
||||
use anyhow::Context;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::is_structured_feature_path;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::ResumeCwdMode;
|
||||
use codex_config::types::SessionPickerViewMode;
|
||||
@@ -323,7 +324,7 @@ impl ConfigDocument {
|
||||
Ok(self.set_skill_config(SkillConfigSelector::Name(name.clone()), *enabled))
|
||||
}
|
||||
ConfigEdit::SetPath { segments, value } => {
|
||||
if is_multi_agent_v2_feature_path(segments) && value.as_bool().is_some() {
|
||||
if is_structured_feature_path(segments) && value.as_bool().is_some() {
|
||||
let mut existing = Some(self.doc.as_item());
|
||||
for segment in segments {
|
||||
existing = existing.and_then(|item| item.as_table_like()?.get(segment));
|
||||
@@ -336,7 +337,26 @@ impl ConfigDocument {
|
||||
}
|
||||
Ok(self.insert(segments, value.clone()))
|
||||
}
|
||||
ConfigEdit::ClearPath { segments } => Ok(self.clear_owned(segments)),
|
||||
ConfigEdit::ClearPath { segments } => {
|
||||
let preserves_broker_settings = is_structured_feature_path(segments)
|
||||
&& segments
|
||||
.last()
|
||||
.is_some_and(|feature| feature == "network_proxy")
|
||||
&& segments
|
||||
.iter()
|
||||
.try_fold(self.doc.as_item(), |item, segment| {
|
||||
item.as_table_like()?.get(segment)
|
||||
})
|
||||
.and_then(TomlItem::as_table_like)
|
||||
.is_some_and(|feature| feature.contains_key("credential_broker"));
|
||||
if preserves_broker_settings {
|
||||
let mut enabled_segments = segments.clone();
|
||||
enabled_segments.push("enabled".to_string());
|
||||
Ok(self.insert(&enabled_segments, value(false)))
|
||||
} else {
|
||||
Ok(self.clear_owned(segments))
|
||||
}
|
||||
}
|
||||
ConfigEdit::SetProjectTrustLevel { path, level } => {
|
||||
// Delegate to the existing, tested logic in config.rs to
|
||||
// ensure tables are explicit and migration is preserved.
|
||||
@@ -618,7 +638,7 @@ impl ConfigDocument {
|
||||
}
|
||||
|
||||
let item = current.get_mut(segment.as_str())?;
|
||||
if is_multi_agent_v2_feature_path(&segments[..=index])
|
||||
if is_structured_feature_path(&segments[..=index])
|
||||
&& let Some(enabled) = item.as_bool()
|
||||
{
|
||||
let mut feature = document_helpers::new_implicit_table();
|
||||
@@ -669,16 +689,6 @@ impl ConfigDocument {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multi_agent_v2_feature_path(segments: &[String]) -> bool {
|
||||
match segments {
|
||||
[features, feature] => features == "features" && feature == "multi_agent_v2",
|
||||
[profiles, _, features, feature] => {
|
||||
profiles == "profiles" && features == "features" && feature == "multi_agent_v2"
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_skill_config_path(path: &Path) -> String {
|
||||
dunce::canonicalize(path)
|
||||
.unwrap_or_else(|_| path.to_path_buf())
|
||||
|
||||
@@ -136,6 +136,37 @@ fn multi_agent_v2_feature_toggle_preserves_nested_configuration() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_proxy_feature_toggle_preserves_credential_broker_configuration() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
for (initial, disabled, enabled_again) in [
|
||||
(
|
||||
"[features]\nnetwork_proxy = true\n",
|
||||
"[features]\n",
|
||||
"[features]\nnetwork_proxy = true\n",
|
||||
),
|
||||
(
|
||||
"[features.network_proxy]\nenabled = true\ncredential_broker = true\ndomains = { \"github.com\" = \"allow\" }\n",
|
||||
"[features.network_proxy]\nenabled = false\ncredential_broker = true\ndomains = { \"github.com\" = \"allow\" }\n",
|
||||
"[features.network_proxy]\nenabled = true\ncredential_broker = true\ndomains = { \"github.com\" = \"allow\" }\n",
|
||||
),
|
||||
] {
|
||||
std::fs::write(&config_path, initial).expect("write config");
|
||||
for (enabled, expected) in [(false, disabled), (true, enabled_again)] {
|
||||
ConfigEditsBuilder::new(codex_home)
|
||||
.set_feature_enabled("network_proxy", enabled)
|
||||
.apply_blocking()
|
||||
.expect("toggle feature");
|
||||
let updated: TomlValue =
|
||||
toml::from_str(&std::fs::read_to_string(&config_path).expect("read config"))
|
||||
.expect("parse config");
|
||||
assert_eq!(updated, toml::from_str::<TomlValue>(expected).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adding nested multi-agent settings must retain an existing legacy boolean toggle.
|
||||
#[test]
|
||||
fn multi_agent_v2_nested_edit_preserves_legacy_boolean_toggle() {
|
||||
|
||||
@@ -3602,6 +3602,8 @@ impl Config {
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config
|
||||
.set_credential_broker_openai_base_url(cfg.openai_base_url.as_deref());
|
||||
configured_network_proxy_config.enabled = true;
|
||||
}
|
||||
if cfg.approval_policy == Some(AskForApproval::UnlessTrusted) {
|
||||
@@ -4475,6 +4477,8 @@ impl Config {
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config
|
||||
.set_credential_broker_openai_base_url(cfg.openai_base_url.as_deref());
|
||||
configured_network_proxy_config.enabled = true;
|
||||
}
|
||||
configured_network_proxy_config
|
||||
|
||||
@@ -189,6 +189,9 @@ pub(crate) fn apply_network_proxy_feature_config(
|
||||
mitm: None,
|
||||
}
|
||||
.apply_to_network_proxy_config(config);
|
||||
if let Some(credential_broker) = feature_config.credential_broker {
|
||||
config.set_credential_broker_enabled(credential_broker);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_permission_profile(
|
||||
|
||||
Reference in new issue
Block a user