mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Honor managed runtime authentication modes and expand coverage (#60014)
## What changed Allow `requirements.toml` to pin `features.runtime_agent_instances.mode` to `dual_header` or `assertion_only`. Apply the required mode when no explicit mode is configured and reject conflicting explicit settings, while preserving legacy boolean requirements. Document experimental runtime authentication configuration, credential lifecycle, and bootstrap authentication behavior. ## Testing Add coverage for managed mode defaults and conflicts, runtime reuse and rotation across account requests and restarts, fresh assertions on Realtime and MCP reconnects, and Realtime destination and environment restrictions. GitOrigin-RevId: bcf9502178f477c1e8232386f1f3e360bf23d667
This commit is contained in:
@@ -49,6 +49,7 @@ fn resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature(
|
||||
let requirements = Sourced::new(
|
||||
FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("secret_auth_storage".to_string(), true)]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::Unknown,
|
||||
);
|
||||
|
||||
@@ -1431,6 +1431,7 @@ personality = true
|
||||
.map(|requirements| requirements.value.clone()),
|
||||
Some(codex_config::FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("personality".to_string(), true)]),
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
let config_requirements: ConfigRequirements = config_requirements_toml.try_into()?;
|
||||
@@ -1471,6 +1472,7 @@ personality = true
|
||||
.map(|requirements| requirements.value.clone()),
|
||||
Some(codex_config::FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("personality".to_string(), true)]),
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
Ok(())
|
||||
|
||||
@@ -2233,6 +2233,7 @@ respect_system_proxy = true
|
||||
let disabled = Sourced::new(
|
||||
FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("respect_system_proxy".to_string(), false)]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::Unknown,
|
||||
);
|
||||
@@ -2255,6 +2256,7 @@ respect_system_proxy = true
|
||||
let enabled = Sourced::new(
|
||||
FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("respect_system_proxy".to_string(), true)]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::Unknown,
|
||||
);
|
||||
@@ -2356,6 +2358,7 @@ async fn system_proxy_fallback_honors_feature_requirements() -> std::io::Result<
|
||||
let requirements = Sourced::new(
|
||||
FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("system_proxy_fallback".to_string(), required)]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::Unknown,
|
||||
);
|
||||
@@ -12454,6 +12457,7 @@ fn retired_feature_requirements_do_not_pin_configured_values(
|
||||
(key.to_string(), required),
|
||||
("shell_tool".to_string(), false),
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::EnterpriseManaged {
|
||||
id: "enterprise-id".to_string(),
|
||||
|
||||
@@ -14,7 +14,9 @@ use codex_config::config_toml::ConfigToml;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeatureConfigSource;
|
||||
use codex_features::FeatureOverrides;
|
||||
use codex_features::FeatureToml;
|
||||
use codex_features::Features;
|
||||
use codex_features::RuntimeAuthMode;
|
||||
use codex_features::canonical_feature_for_key;
|
||||
use codex_features::feature_for_key;
|
||||
|
||||
@@ -359,10 +361,42 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
explicit_settings
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_runtime_auth_mode(
|
||||
cfg: &ConfigToml,
|
||||
requirements: Option<&Sourced<FeatureRequirementsToml>>,
|
||||
) -> std::io::Result<RuntimeAuthMode> {
|
||||
let configured = cfg
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.runtime_agent_instances.as_ref())
|
||||
.and_then(|feature| match feature {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => config.mode,
|
||||
});
|
||||
if let Some(requirements) = requirements
|
||||
&& let Some(required) = requirements.value.runtime_auth_mode
|
||||
{
|
||||
if configured.is_some_and(|mode| mode != required) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
ConstraintError::InvalidValue {
|
||||
field_name: "features.runtime_agent_instances.mode",
|
||||
candidate: format!("{:?}", configured.unwrap_or_default()),
|
||||
allowed: format!("{required:?}"),
|
||||
requirement_source: requirements.source.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
return Ok(required);
|
||||
}
|
||||
Ok(configured.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_explicit_feature_settings_in_config_toml(
|
||||
cfg: &ConfigToml,
|
||||
feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,
|
||||
) -> std::io::Result<()> {
|
||||
resolve_runtime_auth_mode(cfg, feature_requirements)?;
|
||||
let Some(Sourced {
|
||||
value: feature_requirements,
|
||||
source,
|
||||
|
||||
@@ -3619,6 +3619,9 @@ impl Config {
|
||||
{
|
||||
configured_features.enable(Feature::Collab);
|
||||
}
|
||||
let runtime_auth_mode = managed_features::resolve_runtime_auth_mode(
|
||||
&cfg, feature_requirements.as_ref(),
|
||||
)?;
|
||||
let features = ManagedFeatures::from_configured_with_warnings(
|
||||
configured_features,
|
||||
feature_requirements,
|
||||
@@ -4087,15 +4090,6 @@ impl Config {
|
||||
FeatureToml::Config(config) => config.mode,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let runtime_auth_mode = cfg
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.runtime_agent_instances.as_ref())
|
||||
.and_then(|feature| match feature {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => config.mode,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);
|
||||
|
||||
let agent_roles =
|
||||
@@ -5349,3 +5343,7 @@ mod tests;
|
||||
#[cfg(test)]
|
||||
#[path = "config_loader_tests.rs"]
|
||||
mod config_loader_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "runtime_auth_tests.rs"]
|
||||
mod runtime_auth_tests;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
//! Verifies managed runtime-auth defaults and rejection of conflicting user overrides.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use codex_config::RequirementSource;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_runtime_mode_pins_defaults_and_rejects_overrides() -> anyhow::Result<()> {
|
||||
for (name, mode) in [
|
||||
("dual_header", RuntimeAuthMode::DualHeader),
|
||||
("assertion_only", RuntimeAuthMode::AssertionOnly),
|
||||
] {
|
||||
let requirement: codex_config::ConfigRequirementsToml = toml::from_str(&format!(
|
||||
"[features.runtime_agent_instances]\nenabled = true\nmode = '{name}'"
|
||||
))?;
|
||||
for local in ["", "[features]\nruntime_agent_instances = true"] {
|
||||
let mut sourced = codex_config::ConfigRequirementsWithSources::default();
|
||||
sourced.merge_unset_fields(RequirementSource::Unknown, requirement.clone());
|
||||
let stack = codex_config::ConfigLayerStack::new(
|
||||
Vec::new(),
|
||||
sourced.try_into()?,
|
||||
requirement.clone(),
|
||||
)?;
|
||||
let home = tempdir()?;
|
||||
let config = Config::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
toml::from_str(local)?,
|
||||
ConfigOverrides::default(),
|
||||
AbsolutePathBuf::try_from(home.path())?,
|
||||
stack,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(config.runtime_auth_mode, mode);
|
||||
assert!(config.features.enabled(Feature::RuntimeAgentInstances));
|
||||
}
|
||||
let different = if name == "dual_header" {
|
||||
"assertion_only"
|
||||
} else {
|
||||
"dual_header"
|
||||
};
|
||||
let configured = toml::from_str(&format!(
|
||||
"[features.runtime_agent_instances]\nmode = '{different}'"
|
||||
))?;
|
||||
let requirements = Sourced::new(
|
||||
requirement.feature_requirements.clone().unwrap(),
|
||||
RequirementSource::Unknown,
|
||||
);
|
||||
assert!(
|
||||
managed_features::resolve_runtime_auth_mode(&configured, Some(&requirements)).is_err()
|
||||
);
|
||||
assert!(
|
||||
managed_features::validate_explicit_feature_settings_in_config_toml(
|
||||
&configured,
|
||||
Some(&requirements)
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmanaged_runtime_modes_and_legacy_boolean_requirements_work() -> anyhow::Result<()> {
|
||||
for (text, expected) in [
|
||||
("", RuntimeAuthMode::DualHeader),
|
||||
(
|
||||
"[features]\nruntime_agent_instances = true",
|
||||
RuntimeAuthMode::DualHeader,
|
||||
),
|
||||
(
|
||||
"[features.runtime_agent_instances]\nmode = 'assertion_only'",
|
||||
RuntimeAuthMode::AssertionOnly,
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
managed_features::resolve_runtime_auth_mode(
|
||||
&toml::from_str(text)?,
|
||||
/*requirements*/ None
|
||||
)?,
|
||||
expected
|
||||
);
|
||||
}
|
||||
let requirement: codex_config::ConfigRequirementsToml =
|
||||
toml::from_str("[features]\nruntime_agent_instances = false")?;
|
||||
let requirement = requirement.feature_requirements.unwrap();
|
||||
assert_eq!(
|
||||
requirement.entries.get("runtime_agent_instances"),
|
||||
Some(&false)
|
||||
);
|
||||
assert_eq!(requirement.runtime_auth_mode, None);
|
||||
assert!(
|
||||
toml::from_str::<codex_config::ConfigRequirementsToml>(
|
||||
"[features.runtime_agent_instances]\nmode = 'unknown'"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -67,21 +67,5 @@ pub(crate) fn is_openai_realtime_url(base_url: &str) -> CodexResult<bool> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_openai_realtime_url;
|
||||
|
||||
#[test]
|
||||
fn realtime_runtime_auth_rejects_unsupported_openai_origins() {
|
||||
for url in [
|
||||
"ws://api.openai.com/v1",
|
||||
"http://api.openai.com/v1",
|
||||
"wss://api.openai.com:8443/v1",
|
||||
"wss://unknown.openai.com/v1",
|
||||
] {
|
||||
assert!(is_openai_realtime_url(url).is_err(), "{url}");
|
||||
}
|
||||
assert!(is_openai_realtime_url("wss://api.openai.com/v1").unwrap());
|
||||
assert!(is_openai_realtime_url("https://api.openai.org/v1").unwrap());
|
||||
assert!(!is_openai_realtime_url("wss://provider.example.com/v1").unwrap());
|
||||
}
|
||||
}
|
||||
#[path = "runtime_auth_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Verifies the supported destinations and environment boundaries for Realtime auth.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use codex_model_provider::validate_runtime_environment;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn realtime_auth_accepts_only_secure_first_party_destinations() {
|
||||
for url in [
|
||||
"https://api.openai.com/v1",
|
||||
"https://api.openai.org/v1",
|
||||
"wss://api.openai.com/v1",
|
||||
"wss://chatgpt.com/backend-api",
|
||||
] {
|
||||
assert!(is_openai_realtime_url(url).unwrap(), "{url}");
|
||||
}
|
||||
for url in [
|
||||
"ws://api.openai.com/v1",
|
||||
"http://api.openai.com/v1",
|
||||
"wss://unknown.openai.com/v1",
|
||||
"wss://api.openai.com:8443/v1",
|
||||
"wss://user:secret@api.openai.com/v1",
|
||||
] {
|
||||
assert!(is_openai_realtime_url(url).is_err(), "{url}");
|
||||
}
|
||||
assert!(!is_openai_realtime_url("wss://voice.example.com").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn realtime_rejects_a_different_registration_environment() {
|
||||
for (chatgpt_base_url, expected_staging) in [
|
||||
("https://chatgpt.com/backend-api", false),
|
||||
("https://chatgpt-staging.com/backend-api", true),
|
||||
] {
|
||||
let runtime = RuntimeAuthManager::for_chatgpt(
|
||||
AuthManager::from_auth_for_testing(codex_login::CodexAuth::from_api_key("test-key")),
|
||||
uuid::Uuid::nil().to_string(),
|
||||
chatgpt_base_url.to_owned(),
|
||||
RuntimeRegistrationMetadata {
|
||||
harness: None,
|
||||
harness_version: None,
|
||||
role: RuntimeRole::Orchestrator,
|
||||
},
|
||||
);
|
||||
for (destination, staging) in [
|
||||
("wss://api.openai.com/v1", false),
|
||||
("https://chatgpt.com/backend-api", false),
|
||||
("wss://api.openai-staging.com/v1", true),
|
||||
("https://api.openai.org/v1", true),
|
||||
("https://workspace.chatgpt-staging.com/backend-api", true),
|
||||
("https://chatgpt-staging.com/backend-api", true),
|
||||
] {
|
||||
assert_eq!(
|
||||
validate_runtime_environment(&runtime, Some(destination)).is_ok(),
|
||||
staging == expected_staging,
|
||||
"{chatgpt_base_url} -> {destination}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -635,6 +635,7 @@ async fn reviewer_tool_policy_respect_managed_shell_restrictions() {
|
||||
Some(codex_config::Sourced::new(
|
||||
codex_config::FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([(feature.key().to_string(), false)]),
|
||||
..Default::default()
|
||||
},
|
||||
codex_config::RequirementSource::Unknown,
|
||||
)),
|
||||
|
||||
Reference in New Issue
Block a user