mirror of
https://github.com/openai/codex.git
synced 2026-05-05 05:42:33 +03:00
feat(core): add network constraints schema to requirements.toml (#10958)
## Summary Add `requirements.toml` schema support for admin-defined network constraints in the requirements layer example config: ``` [experimental_network] enabled = true allowed_domains = ["api.openai.com"] denied_domains = ["example.com"] ```
This commit is contained in:
@@ -81,6 +81,8 @@ pub struct ConfigRequirements {
|
||||
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
|
||||
pub(crate) exec_policy: Option<Sourced<RequirementsExecPolicy>>,
|
||||
pub enforce_residency: ConstrainedWithSource<Option<ResidencyRequirement>>,
|
||||
/// Managed network constraints derived from requirements.
|
||||
pub network: Option<Sourced<NetworkConstraints>>,
|
||||
}
|
||||
|
||||
impl Default for ConfigRequirements {
|
||||
@@ -101,6 +103,7 @@ impl Default for ConfigRequirements {
|
||||
mcp_servers: None,
|
||||
exec_policy: None,
|
||||
enforce_residency: ConstrainedWithSource::new(Constrained::allow_any(None), None),
|
||||
network: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +126,64 @@ pub struct McpServerRequirement {
|
||||
pub identity: McpServerIdentity,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct NetworkRequirementsToml {
|
||||
pub enabled: Option<bool>,
|
||||
pub http_port: Option<u16>,
|
||||
pub socks_port: Option<u16>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
/// Normalized network constraints derived from requirements TOML.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct NetworkConstraints {
|
||||
pub enabled: Option<bool>,
|
||||
pub http_port: Option<u16>,
|
||||
pub socks_port: Option<u16>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<NetworkRequirementsToml> for NetworkConstraints {
|
||||
fn from(value: NetworkRequirementsToml) -> Self {
|
||||
let NetworkRequirementsToml {
|
||||
enabled,
|
||||
http_port,
|
||||
socks_port,
|
||||
allow_upstream_proxy,
|
||||
dangerously_allow_non_loopback_proxy,
|
||||
dangerously_allow_non_loopback_admin,
|
||||
allowed_domains,
|
||||
denied_domains,
|
||||
allow_unix_sockets,
|
||||
allow_local_binding,
|
||||
} = value;
|
||||
Self {
|
||||
enabled,
|
||||
http_port,
|
||||
socks_port,
|
||||
allow_upstream_proxy,
|
||||
dangerously_allow_non_loopback_proxy,
|
||||
dangerously_allow_non_loopback_admin,
|
||||
allowed_domains,
|
||||
denied_domains,
|
||||
allow_unix_sockets,
|
||||
allow_local_binding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum WebSearchModeRequirement {
|
||||
@@ -170,6 +231,8 @@ pub struct ConfigRequirementsToml {
|
||||
pub mcp_servers: Option<BTreeMap<String, McpServerRequirement>>,
|
||||
pub rules: Option<RequirementsExecPolicyToml>,
|
||||
pub enforce_residency: Option<ResidencyRequirement>,
|
||||
#[serde(rename = "experimental_network")]
|
||||
pub network: Option<NetworkRequirementsToml>,
|
||||
}
|
||||
|
||||
/// Value paired with the requirement source it came from, for better error
|
||||
@@ -202,6 +265,7 @@ pub struct ConfigRequirementsWithSources {
|
||||
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
|
||||
pub rules: Option<Sourced<RequirementsExecPolicyToml>>,
|
||||
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
|
||||
pub network: Option<Sourced<NetworkRequirementsToml>>,
|
||||
}
|
||||
|
||||
impl ConfigRequirementsWithSources {
|
||||
@@ -236,6 +300,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -248,6 +313,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
} = self;
|
||||
ConfigRequirementsToml {
|
||||
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
|
||||
@@ -256,6 +322,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers: mcp_servers.map(|sourced| sourced.value),
|
||||
rules: rules.map(|sourced| sourced.value),
|
||||
enforce_residency: enforce_residency.map(|sourced| sourced.value),
|
||||
network: network.map(|sourced| sourced.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,6 +368,7 @@ impl ConfigRequirementsToml {
|
||||
&& self.mcp_servers.is_none()
|
||||
&& self.rules.is_none()
|
||||
&& self.enforce_residency.is_none()
|
||||
&& self.network.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +383,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
mcp_servers,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
} = toml;
|
||||
|
||||
let approval_policy = match allowed_approval_policies {
|
||||
@@ -471,6 +540,10 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
}
|
||||
None => ConstrainedWithSource::new(Constrained::allow_any(None), None),
|
||||
};
|
||||
let network = network.map(|sourced_network| {
|
||||
let Sourced { value, source } = sourced_network;
|
||||
Sourced::new(NetworkConstraints::from(value), source)
|
||||
});
|
||||
Ok(ConfigRequirements {
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
@@ -478,6 +551,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
mcp_servers,
|
||||
exec_policy,
|
||||
enforce_residency,
|
||||
network,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -506,6 +580,7 @@ mod tests {
|
||||
mcp_servers,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
} = toml;
|
||||
ConfigRequirementsWithSources {
|
||||
allowed_approval_policies: allowed_approval_policies
|
||||
@@ -518,6 +593,7 @@ mod tests {
|
||||
rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
enforce_residency: enforce_residency
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
network: network.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,6 +623,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: Some(enforce_residency),
|
||||
network: None,
|
||||
};
|
||||
|
||||
target.merge_unset_fields(source.clone(), other);
|
||||
@@ -566,6 +643,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
|
||||
network: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -597,6 +675,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
@@ -636,6 +715,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
@@ -951,6 +1031,50 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_requirements_are_preserved_as_constraints_with_source() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
[experimental_network]
|
||||
enabled = true
|
||||
allow_upstream_proxy = false
|
||||
allowed_domains = ["api.example.com", "*.openai.com"]
|
||||
denied_domains = ["blocked.example.com"]
|
||||
allow_unix_sockets = ["/tmp/example.sock"]
|
||||
allow_local_binding = false
|
||||
"#;
|
||||
|
||||
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.enabled, Some(true));
|
||||
assert_eq!(sourced_network.value.allow_upstream_proxy, Some(false));
|
||||
assert_eq!(
|
||||
sourced_network.value.allowed_domains.as_ref(),
|
||||
Some(&vec![
|
||||
"api.example.com".to_string(),
|
||||
"*.openai.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
sourced_network.value.denied_domains.as_ref(),
|
||||
Some(&vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
sourced_network.value.allow_unix_sockets.as_ref(),
|
||||
Some(&vec!["/tmp/example.sock".to_string()])
|
||||
);
|
||||
assert_eq!(sourced_network.value.allow_local_binding, Some(false));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_mcp_server_requirements() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
|
||||
@@ -37,6 +37,8 @@ pub use config_requirements::ConfigRequirementsToml;
|
||||
pub use config_requirements::ConstrainedWithSource;
|
||||
pub use config_requirements::McpServerIdentity;
|
||||
pub use config_requirements::McpServerRequirement;
|
||||
pub use config_requirements::NetworkConstraints;
|
||||
pub use config_requirements::NetworkRequirementsToml;
|
||||
pub use config_requirements::RequirementSource;
|
||||
pub use config_requirements::ResidencyRequirement;
|
||||
pub use config_requirements::SandboxModeRequirement;
|
||||
|
||||
@@ -568,6 +568,7 @@ allowed_approval_policies = ["on-request"]
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
})
|
||||
}),
|
||||
)
|
||||
@@ -615,6 +616,7 @@ allowed_approval_policies = ["on-request"]
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
},
|
||||
);
|
||||
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
|
||||
@@ -651,6 +653,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
};
|
||||
let expected = requirements.clone();
|
||||
let cloud_requirements = CloudRequirementsLoader::new(async move { Some(requirements) });
|
||||
|
||||
Reference in New Issue
Block a user