mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Support header injections in network requirements (#42173)
## What changed - Parse `experimental_network.header_injections` rules with host, method, path-prefix, and header mappings and carry them into `NetworkConstraints`. - Export the rule type from `codex_config`. - Summarize configured rule counts and hosts in the TUI debug configuration without exposing header values. ## Testing - Cover managed configuration parsing and redaction of header values from debug output. GitOrigin-RevId: 623b047e6c6111a9f2ccbe79c9ace054631a3c67
This commit is contained in:
committed by
copyberry
parent
12ed76b09b
commit
671d5d1b35
@@ -428,6 +428,28 @@ pub struct NetworkRequirementsToml {
|
||||
pub managed_allowed_domains_only: Option<bool>,
|
||||
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
/// Requirements-only header injections. These annotate matching requests
|
||||
/// without changing whether non-matching requests are allowed.
|
||||
pub header_injections: Option<Vec<NetworkHeaderInjectionToml>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct NetworkHeaderInjectionToml {
|
||||
pub host: String,
|
||||
pub methods: Vec<String>,
|
||||
pub path_prefixes: Vec<String>,
|
||||
pub headers: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NetworkHeaderInjectionToml {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NetworkHeaderInjectionToml")
|
||||
.field("host", &self.host)
|
||||
.field("methods", &self.methods)
|
||||
.field("path_prefixes", &self.path_prefixes)
|
||||
.field("header_names", &self.headers.keys().collect::<Vec<_>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -450,6 +472,7 @@ struct RawNetworkRequirementsToml {
|
||||
#[serde(default)]
|
||||
allow_unix_sockets: Option<Vec<String>>,
|
||||
allow_local_binding: Option<bool>,
|
||||
header_injections: Option<Vec<NetworkHeaderInjectionToml>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NetworkRequirementsToml {
|
||||
@@ -472,6 +495,7 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
|
||||
unix_sockets,
|
||||
allow_unix_sockets,
|
||||
allow_local_binding,
|
||||
header_injections,
|
||||
} = raw;
|
||||
|
||||
if domains.is_some() && (allowed_domains.is_some() || denied_domains.is_some()) {
|
||||
@@ -499,6 +523,7 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
|
||||
unix_sockets: unix_sockets
|
||||
.or_else(|| legacy_unix_socket_permissions_from_list(allow_unix_sockets)),
|
||||
allow_local_binding,
|
||||
header_injections,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -550,6 +575,7 @@ pub struct NetworkConstraints {
|
||||
pub managed_allowed_domains_only: Option<bool>,
|
||||
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
pub header_injections: Option<Vec<NetworkHeaderInjectionToml>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NetworkConstraints {
|
||||
@@ -575,6 +601,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
|
||||
managed_allowed_domains_only,
|
||||
unix_sockets,
|
||||
allow_local_binding,
|
||||
header_injections,
|
||||
} = value;
|
||||
Self {
|
||||
enabled,
|
||||
@@ -587,6 +614,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
|
||||
managed_allowed_domains_only,
|
||||
unix_sockets,
|
||||
allow_local_binding,
|
||||
header_injections,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4231,6 +4259,14 @@ command = "python3 /enterprise/hooks/pre.py"
|
||||
[experimental_network.unix_sockets]
|
||||
"/tmp/example.sock" = "allow"
|
||||
"/tmp/blocked.sock" = "deny"
|
||||
|
||||
[[experimental_network.header_injections]]
|
||||
host = "api.example.com"
|
||||
methods = ["POST"]
|
||||
path_prefixes = ["/console/v1"]
|
||||
|
||||
[experimental_network.header_injections.headers]
|
||||
"x-statsig-change-source" = "codex"
|
||||
"#;
|
||||
|
||||
let source = RequirementSource::LegacyManagedConfigTomlFromMdm;
|
||||
@@ -4288,6 +4324,21 @@ command = "python3 /enterprise/hooks/pre.py"
|
||||
})
|
||||
);
|
||||
assert_eq!(sourced_network.value.allow_local_binding, Some(false));
|
||||
assert_eq!(
|
||||
sourced_network.value.header_injections,
|
||||
Some(vec![NetworkHeaderInjectionToml {
|
||||
host: "api.example.com".to_string(),
|
||||
methods: vec!["POST".to_string()],
|
||||
path_prefixes: vec!["/console/v1".to_string()],
|
||||
headers: BTreeMap::from([(
|
||||
"x-statsig-change-source".to_string(),
|
||||
"codex".to_string(),
|
||||
)]),
|
||||
}])
|
||||
);
|
||||
let debug = format!("{:?}", sourced_network.value.header_injections);
|
||||
assert!(debug.contains("x-statsig-change-source"));
|
||||
assert!(!debug.contains("codex"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ pub use config_requirements::ModelsRequirementsToml;
|
||||
pub use config_requirements::NetworkConstraints;
|
||||
pub use config_requirements::NetworkDomainPermissionToml;
|
||||
pub use config_requirements::NetworkDomainPermissionsToml;
|
||||
pub use config_requirements::NetworkHeaderInjectionToml;
|
||||
pub use config_requirements::NetworkRequirementsToml;
|
||||
pub use config_requirements::NetworkUnixSocketPermissionToml;
|
||||
pub use config_requirements::NetworkUnixSocketPermissionsToml;
|
||||
|
||||
@@ -3065,7 +3065,7 @@ fn fresh_session_applies_requested_name() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn session_lifecycle_avoids_redundant_subagent_metadata_reads() -> Result<()> {
|
||||
const TEST_STACK_SIZE_BYTES: usize = 8 * 1024 * 1024;
|
||||
const TEST_STACK_SIZE_BYTES: usize = 16 * 1024 * 1024;
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name("tui-session-lifecycle-requests".to_string())
|
||||
|
||||
@@ -568,6 +568,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
|
||||
managed_allowed_domains_only,
|
||||
unix_sockets,
|
||||
allow_local_binding,
|
||||
header_injections,
|
||||
} = network;
|
||||
|
||||
if let Some(enabled) = enabled {
|
||||
@@ -615,6 +616,19 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
|
||||
if let Some(allow_local_binding) = allow_local_binding {
|
||||
parts.push(format!("allow_local_binding={allow_local_binding}"));
|
||||
}
|
||||
if let Some(header_injections) = header_injections {
|
||||
let hosts = header_injections
|
||||
.iter()
|
||||
.map(|rule| rule.host.as_str())
|
||||
.collect::<std::collections::BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
parts.push(format!(
|
||||
"header_injections={} hosts={{{hosts}}}",
|
||||
header_injections.len()
|
||||
));
|
||||
}
|
||||
|
||||
join_or_empty(parts)
|
||||
}
|
||||
@@ -675,6 +689,7 @@ mod tests {
|
||||
use codex_config::NetworkConstraints;
|
||||
use codex_config::NetworkDomainPermissionToml;
|
||||
use codex_config::NetworkDomainPermissionsToml;
|
||||
use codex_config::NetworkHeaderInjectionToml;
|
||||
use codex_config::NetworkUnixSocketPermissionToml;
|
||||
use codex_config::NetworkUnixSocketPermissionsToml;
|
||||
use codex_config::RequirementSource;
|
||||
@@ -914,6 +929,15 @@ interrupt_message = false
|
||||
NetworkDomainPermissionToml::Allow,
|
||||
)]),
|
||||
}),
|
||||
header_injections: Some(vec![NetworkHeaderInjectionToml {
|
||||
host: "api.example.com".to_string(),
|
||||
methods: vec!["POST".to_string()],
|
||||
path_prefixes: vec!["/v1".to_string()],
|
||||
headers: BTreeMap::from([(
|
||||
"x-managed-source".to_string(),
|
||||
"secret-looking-value".to_string(),
|
||||
)]),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::LegacyManagedConfigTomlFromMdm,
|
||||
@@ -1047,8 +1071,9 @@ interrupt_message = false
|
||||
"enforce_residency: us (source: {requirements_source})"
|
||||
)));
|
||||
assert!(rendered.contains(&format!(
|
||||
"experimental_network: enabled=true, domains={{example.com=allow}} (source: {requirements_source})"
|
||||
"experimental_network: enabled=true, domains={{example.com=allow}}, header_injections=1 hosts={{api.example.com}} (source: {requirements_source})"
|
||||
)));
|
||||
assert!(!rendered.contains("secret-looking-value"));
|
||||
assert!(
|
||||
rendered.contains(
|
||||
format!(
|
||||
|
||||
+1
-1
@@ -26,5 +26,5 @@ Requirements:
|
||||
- features: guardian_approval=true (source: MDM managed_config.toml (legacy))
|
||||
- mcp_servers: docs (source: MDM managed_config.toml (legacy))
|
||||
- enforce_residency: us (source: MDM managed_config.toml (legacy))
|
||||
- experimental_network: enabled=true, domains={example.com=allow} (source: MDM managed_config.toml (legacy))
|
||||
- experimental_network: enabled=true, domains={example.com=allow}, header_injections=1 hosts={api.example.com} (source: MDM managed_config.toml (legacy))
|
||||
- permissions.filesystem.deny_read: /home/alice/.gitconfig (source: /etc/codex/requirements.toml)
|
||||
|
||||
Reference in New Issue
Block a user