Compare commits

...

2 Commits

Author SHA1 Message Date
viyatb-oai
5a66a61371 codex: fix CI failure on PR #15966
Co-authored-by: Codex <noreply@openai.com>
2026-03-27 01:27:28 -07:00
viyatb-oai
0f793065de fix: preserve explicit empty legacy network lists
Co-authored-by: Codex <noreply@openai.com>
2026-03-26 23:42:19 -07:00
6 changed files with 161 additions and 16 deletions

View File

@@ -590,6 +590,7 @@ mod tests {
)]),
}),
allow_local_binding: Some(true),
..Default::default()
}),
};
@@ -675,6 +676,7 @@ mod tests {
)]),
}),
allow_local_binding: None,
..Default::default()
}),
};

View File

@@ -233,6 +233,12 @@ pub struct NetworkRequirementsToml {
pub managed_allowed_domains_only: Option<bool>,
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
pub allow_local_binding: Option<bool>,
#[serde(skip)]
#[doc(hidden)]
pub explicit_legacy_allowed_domains: bool,
#[serde(skip)]
#[doc(hidden)]
pub explicit_legacy_denied_domains: bool,
}
#[derive(Deserialize)]
@@ -278,6 +284,8 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
allow_unix_sockets,
allow_local_binding,
} = raw;
let explicit_legacy_allowed_domains = allowed_domains.is_some();
let explicit_legacy_denied_domains = denied_domains.is_some();
if domains.is_some() && (allowed_domains.is_some() || denied_domains.is_some()) {
return Err(D::Error::custom(
@@ -304,6 +312,8 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
unix_sockets: unix_sockets
.or_else(|| legacy_unix_socket_permissions_from_list(allow_unix_sockets)),
allow_local_binding,
explicit_legacy_allowed_domains,
explicit_legacy_denied_domains,
})
}
}
@@ -315,6 +325,7 @@ fn legacy_domain_permissions_from_lists(
allowed_domains: Option<Vec<String>>,
denied_domains: Option<Vec<String>>,
) -> Option<NetworkDomainPermissionsToml> {
let has_legacy_domain_config = allowed_domains.is_some() || denied_domains.is_some();
let mut entries = BTreeMap::new();
for pattern in allowed_domains.unwrap_or_default() {
@@ -325,19 +336,20 @@ fn legacy_domain_permissions_from_lists(
entries.insert(pattern, NetworkDomainPermissionToml::Deny);
}
(!entries.is_empty()).then_some(NetworkDomainPermissionsToml { entries })
has_legacy_domain_config.then_some(NetworkDomainPermissionsToml { entries })
}
fn legacy_unix_socket_permissions_from_list(
allow_unix_sockets: Option<Vec<String>>,
) -> Option<NetworkUnixSocketPermissionsToml> {
let has_legacy_unix_socket_config = allow_unix_sockets.is_some();
let entries = allow_unix_sockets
.unwrap_or_default()
.into_iter()
.map(|path| (path, NetworkUnixSocketPermissionToml::Allow))
.collect::<BTreeMap<_, _>>();
(!entries.is_empty()).then_some(NetworkUnixSocketPermissionsToml { entries })
has_legacy_unix_socket_config.then_some(NetworkUnixSocketPermissionsToml { entries })
}
/// Normalized network constraints derived from requirements TOML.
@@ -355,6 +367,12 @@ pub struct NetworkConstraints {
pub managed_allowed_domains_only: Option<bool>,
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
pub allow_local_binding: Option<bool>,
#[serde(skip)]
#[doc(hidden)]
pub explicit_legacy_allowed_domains: bool,
#[serde(skip)]
#[doc(hidden)]
pub explicit_legacy_denied_domains: bool,
}
impl<'de> Deserialize<'de> for NetworkConstraints {
@@ -367,6 +385,16 @@ impl<'de> Deserialize<'de> for NetworkConstraints {
}
}
impl NetworkConstraints {
pub fn explicit_legacy_allowed_domains(&self) -> bool {
self.explicit_legacy_allowed_domains
}
pub fn explicit_legacy_denied_domains(&self) -> bool {
self.explicit_legacy_denied_domains
}
}
impl From<NetworkRequirementsToml> for NetworkConstraints {
fn from(value: NetworkRequirementsToml) -> Self {
let NetworkRequirementsToml {
@@ -380,6 +408,8 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
managed_allowed_domains_only,
unix_sockets,
allow_local_binding,
explicit_legacy_allowed_domains,
explicit_legacy_denied_domains,
} = value;
Self {
enabled,
@@ -392,6 +422,8 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
managed_allowed_domains_only,
unix_sockets,
allow_local_binding,
explicit_legacy_allowed_domains,
explicit_legacy_denied_domains,
}
}
}
@@ -1842,6 +1874,41 @@ guardian_developer_instructions = """
);
}
#[test]
fn empty_legacy_network_lists_are_preserved_in_canonical_constraints() -> Result<()> {
let toml_str = r#"
[experimental_network]
allowed_domains = []
denied_domains = []
allow_unix_sockets = []
"#;
let source = RequirementSource::CloudRequirements;
let mut requirements_with_sources = ConfigRequirementsWithSources::default();
requirements_with_sources.merge_unset_fields(source.clone(), from_str(toml_str)?);
let requirements = ConfigRequirements::try_from(requirements_with_sources)?;
let sourced_network = requirements
.network
.expect("network requirements should be preserved as constraints");
assert_eq!(sourced_network.source, source);
assert_eq!(
sourced_network.value.domains,
Some(NetworkDomainPermissionsToml {
entries: BTreeMap::new(),
})
);
assert_eq!(
sourced_network.value.unix_sockets,
Some(NetworkUnixSocketPermissionsToml {
entries: BTreeMap::new(),
})
);
Ok(())
}
#[test]
fn network_permission_containers_project_allowed_and_denied_entries() {
let domains = NetworkDomainPermissionsToml {

View File

@@ -225,20 +225,21 @@ impl NetworkProxySpec {
constraints.dangerously_allow_all_unix_sockets =
Some(dangerously_allow_all_unix_sockets);
}
let managed_allowed_domains = if hard_deny_allowlist_misses {
Some(
let managed_allowed_domains =
if hard_deny_allowlist_misses || requirements.explicit_legacy_allowed_domains() {
Some(
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
.unwrap_or_default(),
)
} else {
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
.unwrap_or_default(),
)
} else {
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
};
};
if let Some(managed_allowed_domains) = managed_allowed_domains {
// Managed requirements seed the baseline allowlist. User additions
// can extend that baseline unless managed-only mode pins the
@@ -257,10 +258,20 @@ impl NetworkProxySpec {
constraints.allowed_domains = Some(managed_allowed_domains);
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
}
let managed_denied_domains = requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::denied_domains);
let managed_denied_domains = if requirements.explicit_legacy_denied_domains() {
Some(
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::denied_domains)
.unwrap_or_default(),
)
} else {
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::denied_domains)
};
if let Some(managed_denied_domains) = managed_denied_domains {
let effective_denied_domains = if denylist_expansion_enabled {
Self::merge_domain_lists(

View File

@@ -178,6 +178,69 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false));
}
#[test]
fn explicit_empty_legacy_allowed_domains_pin_full_access_allowlist_empty() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements: NetworkConstraints =
toml::from_str("allowed_domains = []").expect("legacy allowlist should parse");
let spec = NetworkProxySpec::from_config_and_constraints(
config,
Some(requirements),
&SandboxPolicy::DangerFullAccess,
)
.expect("explicit empty legacy allowlist should remain constraining");
assert_eq!(spec.config.network.allowed_domains(), None);
assert_eq!(spec.constraints.allowed_domains, Some(Vec::new()));
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
}
#[test]
fn explicit_empty_legacy_denied_domains_pin_full_access_denylist_empty() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_denied_domains(vec!["blocked.example.com".to_string()]);
let requirements: NetworkConstraints =
toml::from_str("denied_domains = []").expect("legacy denylist should parse");
let spec = NetworkProxySpec::from_config_and_constraints(
config,
Some(requirements),
&SandboxPolicy::DangerFullAccess,
)
.expect("explicit empty legacy denylist should remain constraining");
assert_eq!(spec.config.network.denied_domains(), None);
assert_eq!(spec.constraints.denied_domains, Some(Vec::new()));
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false));
}
#[test]
fn explicit_empty_legacy_allow_unix_sockets_disables_allow_all_unix_sockets() {
let mut config = NetworkProxyConfig::default();
config.network.dangerously_allow_all_unix_sockets = true;
let requirements: NetworkConstraints =
toml::from_str("allow_unix_sockets = []").expect("legacy unix socket list should parse");
let err = NetworkProxySpec::from_config_and_constraints(
config,
Some(requirements),
&SandboxPolicy::DangerFullAccess,
)
.expect_err("explicit empty legacy unix socket allowlist should remain constraining");
assert!(
err.to_string()
.contains("network proxy constraints are invalid: invalid value for network.dangerously_allow_all_unix_sockets"),
"unexpected error: {err:#}"
);
}
#[test]
fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
let mut config = NetworkProxyConfig::default();

View File

@@ -339,6 +339,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
managed_allowed_domains_only,
unix_sockets,
allow_local_binding,
..
} = network;
if let Some(enabled) = enabled {

View File

@@ -339,6 +339,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
managed_allowed_domains_only,
unix_sockets,
allow_local_binding,
..
} = network;
if let Some(enabled) = enabled {