mirror of
https://github.com/openai/codex.git
synced 2026-11-10 08:43:33 +00:00
Enforce managed residency for model providers (#39645)
## Why Model provider configuration could override the residency header required by `enforce_residency`, so provider-backed requests did not reliably honor the managed setting. ## What changed - Apply the managed residency header after building a provider, making it authoritative for model requests and model discovery. - Warn when a provider configures the residency header through `http_headers` or `env_http_headers`, while preserving the original provider configuration and unrelated headers. - Cover HTTP and WebSocket requests, model discovery, and case-insensitive header detection with targeted tests. GitOrigin-RevId: 461ef8989d5bd06ffdea694aee4265e392e07590
This commit is contained in:
1 parent
663da53823
commit
af0e82c562
7 files changed
+371
-3
No files matched your search
@@ -73,6 +73,7 @@ use codex_config::types::WindowsToml;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_login::default_client::RESIDENCY_HEADER_NAME;
|
||||
use codex_login::test_support::auth_manager_from_optional_auth;
|
||||
use codex_model_provider::ProviderCapabilities;
|
||||
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
@@ -947,6 +948,88 @@ profile = "codex-bedrock"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_residency_warns_about_provider_header_overrides_without_mutating_config()
|
||||
-> std::io::Result<()> {
|
||||
for enforce_residency in [None, Some(ResidencyRequirement::Us)] {
|
||||
let cfg = toml::from_str::<ConfigToml>(
|
||||
r#"
|
||||
model_provider = "custom-openai"
|
||||
|
||||
[model_providers.custom-openai]
|
||||
name = "OpenAI"
|
||||
http_headers = { "X-OpenAI-Internal-Codex-Residency" = "request-override", "x-provider-header" = "preserved" }
|
||||
env_http_headers = { "x-openai-internal-codex-residency" = "CODEX_TEST_UNSET_RESIDENCY_HEADER", "x-provider-env-header" = "CODEX_TEST_UNSET_PROVIDER_HEADER" }
|
||||
"#,
|
||||
)
|
||||
.expect("existing provider configuration should remain loadable");
|
||||
let requirements = ConfigRequirements {
|
||||
enforce_residency: ConstrainedWithSource::new(
|
||||
Constrained::allow_only(enforce_residency),
|
||||
enforce_residency.map(|_| RequirementSource::Unknown),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
enforce_residency,
|
||||
..Default::default()
|
||||
};
|
||||
let config_layer_stack =
|
||||
ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)?;
|
||||
let config = Config::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
cfg,
|
||||
ConfigOverrides::default(),
|
||||
tempdir()?.abs(),
|
||||
config_layer_stack,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let static_headers = config
|
||||
.model_provider
|
||||
.http_headers
|
||||
.as_ref()
|
||||
.expect("static headers should remain configured");
|
||||
let environment_headers = config
|
||||
.model_provider
|
||||
.env_http_headers
|
||||
.as_ref()
|
||||
.expect("environment-backed headers should remain configured");
|
||||
assert_eq!(
|
||||
static_headers
|
||||
.get("X-OpenAI-Internal-Codex-Residency")
|
||||
.map(String::as_str),
|
||||
Some("request-override")
|
||||
);
|
||||
assert_eq!(
|
||||
environment_headers
|
||||
.get(RESIDENCY_HEADER_NAME)
|
||||
.map(String::as_str),
|
||||
Some("CODEX_TEST_UNSET_RESIDENCY_HEADER")
|
||||
);
|
||||
assert_eq!(
|
||||
static_headers.get("x-provider-header").map(String::as_str),
|
||||
Some("preserved")
|
||||
);
|
||||
assert_eq!(
|
||||
environment_headers
|
||||
.get("x-provider-env-header")
|
||||
.map(String::as_str),
|
||||
Some("CODEX_TEST_UNSET_PROVIDER_HEADER")
|
||||
);
|
||||
|
||||
let expected_warning = format!(
|
||||
"Ignoring `{RESIDENCY_HEADER_NAME}` in `model_providers.custom-openai` because managed residency is required."
|
||||
);
|
||||
assert_eq!(
|
||||
config.startup_warnings.contains(&expected_warning),
|
||||
enforce_residency.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_amazon_bedrock_aws_profile_override() {
|
||||
let cfg = toml::from_str::<ConfigToml>(
|
||||
|
||||
@@ -5,6 +5,7 @@ use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::types::ApprovalsReviewer;
|
||||
use codex_config::types::FeedbackConfigToml;
|
||||
use codex_features::FeatureToml;
|
||||
use codex_login::default_client::RESIDENCY_HEADER_NAME;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -47,6 +48,26 @@ pub(super) fn apply_to_config(
|
||||
requirements.feedback.as_ref(),
|
||||
startup_warnings,
|
||||
);
|
||||
if requirements.enforce_residency.value().is_some() {
|
||||
for (provider_name, provider) in &config.model_providers {
|
||||
let has_residency_header = [&provider.http_headers, &provider.env_http_headers]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|headers| {
|
||||
headers
|
||||
.keys()
|
||||
.any(|name| name.eq_ignore_ascii_case(RESIDENCY_HEADER_NAME))
|
||||
});
|
||||
|
||||
if has_residency_header {
|
||||
let warning = format!(
|
||||
"Ignoring `{RESIDENCY_HEADER_NAME}` in `model_providers.{provider_name}` because managed residency is required."
|
||||
);
|
||||
tracing::warn!(provider = provider_name, "{warning}");
|
||||
startup_warnings.push(warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(requirement) = requirements.windows_sandbox_private_desktop.as_ref() {
|
||||
apply_exact_requirement(
|
||||
"windows.sandbox_private_desktop",
|
||||
|
||||
Reference in new issue
Block a user