Remove the network proxy config wrapper (#31767)

## Why

`NetworkProxyConfig` only wrapped `NetworkProxySettings` in a single
`network` field. That extra level made runtime callers repeat `.network`
everywhere without representing a real boundary.

## What changed

- move the managed-network fields directly onto `NetworkProxyConfig`
- collapse the matching partial-config wrapper
- update runtime callers and tests to use the direct fields
- keep the user-facing permissions/profile TOML layout unchanged

The internal serialized shape now matches the runtime type itself. This
does not change managed-network behavior or the `config.toml` shape.
This commit is contained in:
jif
2026-07-09 17:33:37 +01:00
committed by GitHub
parent f46b53bd85
commit cf6d8ad921
25 changed files with 428 additions and 632 deletions
+15 -19
View File
@@ -501,40 +501,39 @@ impl NetworkMitmActionToml {
impl NetworkToml {
pub fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig) {
if let Some(enabled) = self.enabled {
config.network.enabled = enabled;
config.enabled = enabled;
}
if let Some(proxy_url) = self.proxy_url.as_ref() {
config.network.proxy_url = proxy_url.clone();
config.proxy_url = proxy_url.clone();
}
if let Some(enable_socks5) = self.enable_socks5 {
config.network.enable_socks5 = enable_socks5;
config.enable_socks5 = enable_socks5;
}
if let Some(socks_url) = self.socks_url.as_ref() {
config.network.socks_url = socks_url.clone();
config.socks_url = socks_url.clone();
}
if let Some(enable_socks5_udp) = self.enable_socks5_udp {
config.network.enable_socks5_udp = enable_socks5_udp;
config.enable_socks5_udp = enable_socks5_udp;
}
if let Some(allow_upstream_proxy) = self.allow_upstream_proxy {
config.network.allow_upstream_proxy = allow_upstream_proxy;
config.allow_upstream_proxy = allow_upstream_proxy;
}
if let Some(dangerously_allow_non_loopback_proxy) =
self.dangerously_allow_non_loopback_proxy
{
config.network.dangerously_allow_non_loopback_proxy =
dangerously_allow_non_loopback_proxy;
config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy;
}
if let Some(dangerously_allow_all_unix_sockets) = self.dangerously_allow_all_unix_sockets {
config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
}
if let Some(mode) = self.mode {
config.network.mode = mode;
config.mode = mode;
}
if let Some(domains) = self.domains.as_ref() {
overlay_network_domain_permissions(config, domains);
}
if let Some(unix_sockets) = self.unix_sockets.as_ref() {
let mut proxy_unix_sockets = config.network.unix_sockets.take().unwrap_or_default();
let mut proxy_unix_sockets = config.unix_sockets.take().unwrap_or_default();
for (path, permission) in &unix_sockets.entries {
let permission = match permission {
NetworkUnixSocketPermissionToml::Allow => {
@@ -544,17 +543,16 @@ impl NetworkToml {
};
proxy_unix_sockets.entries.insert(path.clone(), permission);
}
config.network.unix_sockets =
config.unix_sockets =
(!proxy_unix_sockets.entries.is_empty()).then_some(proxy_unix_sockets);
}
if let Some(allow_local_binding) = self.allow_local_binding {
config.network.allow_local_binding = allow_local_binding;
config.allow_local_binding = allow_local_binding;
}
if let Some(mitm) = self.mitm.as_ref() {
config.network.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref());
config.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref());
}
config.network.mitm =
config.network.mode == NetworkMode::Limited || !config.network.mitm_hooks.is_empty();
config.mitm = config.mode == NetworkMode::Limited || !config.mitm_hooks.is_empty();
}
pub fn to_network_proxy_config(&self) -> NetworkProxyConfig {
@@ -628,8 +626,6 @@ pub fn overlay_network_domain_permissions(
NetworkDomainPermissionToml::Allow => ProxyNetworkDomainPermission::Allow,
NetworkDomainPermissionToml::Deny => ProxyNetworkDomainPermission::Deny,
};
config
.network
.upsert_domain_permission(pattern.clone(), permission, normalize_host);
config.upsert_domain_permission(pattern.clone(), permission, normalize_host);
}
}
+9 -9
View File
@@ -1182,16 +1182,16 @@ fn permissions_profile_network_to_proxy_config_preserves_mitm_hooks() {
let config = network.to_network_proxy_config();
assert_eq!(config.network.mode, NetworkMode::Full);
assert!(config.network.mitm);
assert_eq!(config.network.mitm_hooks.len(), 1);
assert_eq!(config.network.mitm_hooks[0].host, "api.github.com");
assert_eq!(config.mode, NetworkMode::Full);
assert!(config.mitm);
assert_eq!(config.mitm_hooks.len(), 1);
assert_eq!(config.mitm_hooks[0].host, "api.github.com");
assert_eq!(
config.network.mitm_hooks[0].matcher.methods,
config.mitm_hooks[0].matcher.methods,
vec!["POST".to_string()]
);
assert_eq!(
config.network.mitm_hooks[0].actions.strip_request_headers,
config.mitm_hooks[0].actions.strip_request_headers,
vec!["authorization".to_string()]
);
}
@@ -1228,13 +1228,13 @@ action = ["noop"]
let config = network.to_network_proxy_config();
assert_eq!(config.network.mitm_hooks.len(), 2);
assert_eq!(config.mitm_hooks.len(), 2);
assert_eq!(
config.network.mitm_hooks[0].matcher.path_prefixes,
config.mitm_hooks[0].matcher.path_prefixes,
vec!["/repos/openai/".to_string()]
);
assert_eq!(
config.network.mitm_hooks[1].matcher.path_prefixes,
config.mitm_hooks[1].matcher.path_prefixes,
vec!["/repos/".to_string()]
);
}
+2 -2
View File
@@ -3368,7 +3368,7 @@ impl Config {
network_proxy,
);
}
configured_network_proxy_config.network.enabled = true;
configured_network_proxy_config.enabled = true;
}
let approval_policy_was_explicit =
approval_policy_override.is_some() || cfg.approval_policy.is_some();
@@ -4128,7 +4128,7 @@ impl Config {
network_proxy,
);
}
configured_network_proxy_config.network.enabled = true;
configured_network_proxy_config.enabled = true;
}
configured_network_proxy_config
} else {
+16 -21
View File
@@ -74,15 +74,15 @@ impl ConfigReloader for StaticNetworkProxyReloader {
impl NetworkProxySpec {
pub(crate) fn enabled(&self) -> bool {
self.config.network.enabled
self.config.enabled
}
pub fn proxy_host_and_port(&self) -> String {
host_and_port_from_network_addr(&self.config.network.proxy_url, /*default_port*/ 3128)
host_and_port_from_network_addr(&self.config.proxy_url, /*default_port*/ 3128)
}
pub fn socks_enabled(&self) -> bool {
self.config.network.enable_socks5
self.config.enable_socks5
}
pub(crate) fn from_config_and_constraints(
@@ -221,31 +221,30 @@ impl NetworkProxySpec {
let denylist_expansion_enabled = Self::denylist_expansion_enabled(permission_profile);
if let Some(enabled) = requirements.enabled {
config.network.enabled = enabled;
config.enabled = enabled;
constraints.enabled = Some(enabled);
}
if let Some(http_port) = requirements.http_port {
config.network.proxy_url = format!("http://127.0.0.1:{http_port}");
config.proxy_url = format!("http://127.0.0.1:{http_port}");
}
if let Some(socks_port) = requirements.socks_port {
config.network.socks_url = format!("http://127.0.0.1:{socks_port}");
config.socks_url = format!("http://127.0.0.1:{socks_port}");
}
if let Some(allow_upstream_proxy) = requirements.allow_upstream_proxy {
config.network.allow_upstream_proxy = allow_upstream_proxy;
config.allow_upstream_proxy = allow_upstream_proxy;
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
}
if let Some(dangerously_allow_non_loopback_proxy) =
requirements.dangerously_allow_non_loopback_proxy
{
config.network.dangerously_allow_non_loopback_proxy =
dangerously_allow_non_loopback_proxy;
config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy;
constraints.dangerously_allow_non_loopback_proxy =
Some(dangerously_allow_non_loopback_proxy);
}
if let Some(dangerously_allow_all_unix_sockets) =
requirements.dangerously_allow_all_unix_sockets
{
config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
constraints.dangerously_allow_all_unix_sockets =
Some(dangerously_allow_all_unix_sockets);
}
@@ -270,14 +269,12 @@ impl NetworkProxySpec {
let effective_allowed_domains = if allowlist_expansion_enabled {
Self::merge_domain_lists(
managed_allowed_domains.clone(),
config.network.allowed_domains().as_deref().unwrap_or(&[]),
config.allowed_domains().as_deref().unwrap_or(&[]),
)
} else {
managed_allowed_domains.clone()
};
config
.network
.set_allowed_domains(effective_allowed_domains);
config.set_allowed_domains(effective_allowed_domains);
constraints.allowed_domains = Some(managed_allowed_domains);
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
}
@@ -289,12 +286,12 @@ impl NetworkProxySpec {
let effective_denied_domains = if denylist_expansion_enabled {
Self::merge_domain_lists(
managed_denied_domains.clone(),
config.network.denied_domains().as_deref().unwrap_or(&[]),
config.denied_domains().as_deref().unwrap_or(&[]),
)
} else {
managed_denied_domains.clone()
};
config.network.set_denied_domains(effective_denied_domains);
config.set_denied_domains(effective_denied_domains);
constraints.denied_domains = Some(managed_denied_domains);
constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled);
}
@@ -304,13 +301,11 @@ impl NetworkProxySpec {
.as_ref()
.map(codex_config::NetworkUnixSocketPermissionsToml::allow_unix_sockets)
.unwrap_or_default();
config
.network
.set_allow_unix_sockets(allow_unix_sockets.clone());
config.set_allow_unix_sockets(allow_unix_sockets.clone());
constraints.allow_unix_sockets = Some(allow_unix_sockets);
}
if let Some(allow_local_binding) = requirements.allow_local_binding {
config.network.allow_local_binding = allow_local_binding;
config.allow_local_binding = allow_local_binding;
constraints.allow_local_binding = Some(allow_local_binding);
}
@@ -359,7 +354,7 @@ fn upsert_network_domains(config: &mut NetworkProxyConfig, hosts: Vec<String>, a
let mut incoming = HashSet::new();
for host in hosts {
if incoming.insert(host.clone()) {
config.network.upsert_domain_permission(
config.upsert_domain_permission(
host,
if allow {
codex_network_proxy::NetworkDomainPermission::Allow
@@ -43,9 +43,7 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() {
#[test]
fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"*.example.com",
@@ -62,7 +60,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
.expect("config should stay within the managed allowlist");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec![
"*.example.com".to_string(),
"api.example.com".to_string()
@@ -78,9 +76,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
#[test]
fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_denied_domains(vec!["api.example.com".to_string()]);
config.set_denied_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"api.example.com",
@@ -96,9 +92,9 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
)
.expect("managed allowlist should not erase a user deny");
assert_eq!(spec.config.network.allowed_domains(), None);
assert_eq!(spec.config.allowed_domains(), None);
assert_eq!(
spec.config.network.denied_domains(),
spec.config.denied_domains(),
Some(vec!["api.example.com".to_string()])
);
assert_eq!(
@@ -110,9 +106,7 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
#[test]
fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"*.example.com",
@@ -129,18 +123,18 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
.expect("managed baseline should still allow user edits");
let mut candidate = spec.config.clone();
candidate.network.upsert_domain_permission(
candidate.upsert_domain_permission(
"api.example.com".to_string(),
NetworkDomainPermission::Deny,
normalize_host,
);
assert_eq!(
candidate.network.allowed_domains(),
candidate.allowed_domains(),
Some(vec!["*.example.com".to_string()])
);
assert_eq!(
candidate.network.denied_domains(),
candidate.denied_domains(),
Some(vec!["api.example.com".to_string()])
);
validate_policy_against_constraints(&candidate, &spec.constraints)
@@ -150,9 +144,7 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
#[test]
fn managed_unrestricted_profile_allows_domain_expansion() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"*.example.com",
@@ -173,7 +165,7 @@ fn managed_unrestricted_profile_allows_domain_expansion() {
.expect("managed unrestricted filesystem should still use managed network constraints");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec![
"*.example.com".to_string(),
"api.example.com".to_string()
@@ -185,12 +177,8 @@ fn managed_unrestricted_profile_allows_domain_expansion() {
#[test]
fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["evil.com".to_string()]);
config
.network
.set_denied_domains(vec!["more-blocked.example.com".to_string()]);
config.set_allowed_domains(vec!["evil.com".to_string()]);
config.set_denied_domains(vec!["more-blocked.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([
("*.example.com", NetworkDomainPermissionToml::Allow),
@@ -207,11 +195,11 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
.expect("yolo mode should pin the effective policy to the managed baseline");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec!["*.example.com".to_string()])
);
assert_eq!(
spec.config.network.denied_domains(),
spec.config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
@@ -221,9 +209,7 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
#[test]
fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"*.example.com",
@@ -241,7 +227,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
.expect("managed baseline should still load");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec!["*.example.com".to_string()])
);
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
@@ -250,9 +236,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
#[test]
fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"managed.example.com",
@@ -270,7 +254,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
.expect("managed-only allowlist should still load");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec!["managed.example.com".to_string()])
);
assert_eq!(
@@ -284,9 +268,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
#[test]
fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
managed_allowed_domains_only: Some(true),
..Default::default()
@@ -299,7 +281,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
)
.expect("managed-only mode should treat missing managed allowlist as empty");
assert_eq!(spec.config.network.allowed_domains(), None);
assert_eq!(spec.config.allowed_domains(), None);
assert_eq!(spec.constraints.allowed_domains, Some(Vec::new()));
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
assert!(spec.hard_deny_allowlist_misses);
@@ -308,9 +290,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
#[test]
fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_managed_list() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
managed_allowed_domains_only: Some(true),
..Default::default()
@@ -323,7 +303,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
)
.expect("managed-only mode should treat missing managed allowlist as empty");
assert_eq!(spec.config.network.allowed_domains(), None);
assert_eq!(spec.config.allowed_domains(), None);
assert_eq!(spec.constraints.allowed_domains, Some(Vec::new()));
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
assert!(spec.hard_deny_allowlist_misses);
@@ -332,9 +312,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
#[test]
fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["api.example.com".to_string()]);
config.set_allowed_domains(vec!["api.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"managed-blocked.example.com",
@@ -351,13 +329,13 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
.expect("deny-only requirements should not constrain the allowlist");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec!["api.example.com".to_string()])
);
assert_eq!(spec.constraints.allowed_domains, None);
assert_eq!(spec.constraints.allowlist_expansion_enabled, None);
assert_eq!(
spec.config.network.denied_domains(),
spec.config.denied_domains(),
Some(vec!["managed-blocked.example.com".to_string()])
);
}
@@ -365,9 +343,7 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
#[test]
fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_denied_domains(vec!["blocked.example.com".to_string()]);
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"managed.example.com",
@@ -384,11 +360,11 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
.expect("allow-only requirements should not constrain the denylist");
assert_eq!(
spec.config.network.allowed_domains(),
spec.config.allowed_domains(),
Some(vec!["managed.example.com".to_string()])
);
assert_eq!(
spec.config.network.denied_domains(),
spec.config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
assert_eq!(spec.constraints.denied_domains, None);
@@ -398,9 +374,7 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
#[test]
fn requirements_denied_domains_are_a_baseline_for_default_mode() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_denied_domains(vec!["blocked.example.com".to_string()]);
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"managed-blocked.example.com",
@@ -417,7 +391,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
.expect("default mode should merge managed and user deny entries");
assert_eq!(
spec.config.network.denied_domains(),
spec.config.denied_domains(),
Some(vec![
"managed-blocked.example.com".to_string(),
"blocked.example.com".to_string()
@@ -433,9 +407,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
#[test]
fn requirements_denylist_expansion_keeps_user_entries_mutable() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_denied_domains(vec!["blocked.example.com".to_string()]);
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(domain_permissions([(
"managed-blocked.example.com",
@@ -452,18 +424,18 @@ fn requirements_denylist_expansion_keeps_user_entries_mutable() {
.expect("managed baseline should still allow user edits");
let mut candidate = spec.config.clone();
candidate.network.upsert_domain_permission(
candidate.upsert_domain_permission(
"blocked.example.com".to_string(),
NetworkDomainPermission::Allow,
normalize_host,
);
assert_eq!(
candidate.network.allowed_domains(),
candidate.allowed_domains(),
Some(vec!["blocked.example.com".to_string()])
);
assert_eq!(
candidate.network.denied_domains(),
candidate.denied_domains(),
Some(vec!["managed-blocked.example.com".to_string()])
);
validate_policy_against_constraints(&candidate, &spec.constraints)
+1 -1
View File
@@ -127,7 +127,7 @@ pub(crate) fn network_proxy_config_from_profile_network(
// Profile `network.enabled` controls sandbox network access. Profiles may
// provide proxy settings for the feature gate to consume when that network
// access is enabled, but they do not start the managed proxy on their own.
config.network.enabled = false;
config.enabled = false;
config
}
@@ -220,7 +220,7 @@ fn network_toml_overlays_unix_socket_permissions_by_path() {
.apply_to_network_proxy_config(&mut config);
assert_eq!(
config.network.unix_sockets,
config.unix_sockets,
Some(codex_network_proxy::NetworkUnixSocketPermissions {
entries: BTreeMap::from([
(
@@ -399,7 +399,7 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_bare_network_access() {
..Default::default()
}));
assert!(!config.network.enabled);
assert!(!config.enabled);
}
#[test]
@@ -417,11 +417,11 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_proxy_policy() {
..Default::default()
}));
assert!(!config.network.enabled);
assert_eq!(config.network.proxy_url, "http://127.0.0.1:43128");
assert!(!config.network.enable_socks5);
assert!(!config.enabled);
assert_eq!(config.proxy_url, "http://127.0.0.1:43128");
assert!(!config.enable_socks5);
assert_eq!(
config.network.domains,
config.domains,
Some(codex_network_proxy::NetworkDomainPermissions {
entries: vec![codex_network_proxy::NetworkDomainPermissionEntry {
pattern: "openai.com".to_string(),
+10 -10
View File
@@ -2974,11 +2974,11 @@ async fn guardian_review_session_config_clears_legacy_notify() {
#[tokio::test]
async fn guardian_review_session_config_uses_live_network_proxy_state() {
let mut parent_config = test_config().await;
let mut parent_network = NetworkProxyConfig::default();
parent_network.network.enabled = true;
parent_network
.network
.set_allowed_domains(vec!["parent.example".to_string()]);
let mut parent_network = NetworkProxyConfig {
enabled: true,
..Default::default()
};
parent_network.set_allowed_domains(vec!["parent.example".to_string()]);
parent_config.permissions.network = Some(
NetworkProxySpec::from_config_and_constraints(
parent_network,
@@ -2988,11 +2988,11 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() {
.expect("parent network proxy spec"),
);
let mut live_network = NetworkProxyConfig::default();
live_network.network.enabled = true;
live_network
.network
.set_allowed_domains(vec!["github.com".to_string()]);
let mut live_network = NetworkProxyConfig {
enabled: true,
..Default::default()
};
live_network.set_allowed_domains(vec!["github.com".to_string()]);
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
+8 -10
View File
@@ -162,14 +162,14 @@ fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProx
if let Some(domains) = network.domains.as_ref() {
let mut config = NetworkProxyConfig::default();
if let Some(allowed_domains) = constraints.allowed_domains.take() {
config.network.set_allowed_domains(allowed_domains);
config.set_allowed_domains(allowed_domains);
}
if let Some(denied_domains) = constraints.denied_domains.take() {
config.network.set_denied_domains(denied_domains);
config.set_denied_domains(denied_domains);
}
overlay_network_domain_permissions(&mut config, domains);
constraints.allowed_domains = config.network.allowed_domains();
constraints.denied_domains = config.network.denied_domains();
constraints.allowed_domains = config.allowed_domains();
constraints.denied_domains = config.denied_domains();
}
if let Some(unix_sockets) = network.unix_sockets.as_ref() {
let allow_unix_sockets = unix_sockets.allow_unix_sockets();
@@ -256,11 +256,11 @@ impl NetworkConfigAccumulator {
};
mitm.validate_action_references(&actions)
.map_err(anyhow::Error::msg)?;
self.config.network.mitm_hooks = mitm.to_runtime_hooks(Some(&actions));
self.config.mitm_hooks = mitm.to_runtime_hooks(Some(&actions));
}
self.config.network.mitm = self.config.network.mode == NetworkMode::Limited
|| !self.config.network.mitm_hooks.is_empty();
self.config.mitm =
self.config.mode == NetworkMode::Limited || !self.config.mitm_hooks.is_empty();
Ok(self.config)
}
}
@@ -310,9 +310,7 @@ fn upsert_network_domain(
host: String,
permission: codex_network_proxy::NetworkDomainPermission,
) {
config
.network
.upsert_domain_permission(host, permission, normalize_host);
config.upsert_domain_permission(host, permission, normalize_host);
}
fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
+21 -25
View File
@@ -57,14 +57,14 @@ default_permissions = "dev"
.expect("higher layer should apply");
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec![
"lower.example.com".to_string(),
"higher.example.com".to_string()
])
);
assert_eq!(
config.network.denied_domains(),
config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
}
@@ -108,13 +108,13 @@ default_permissions = "dev"
.expect("higher layer should apply");
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec![
"other.example.com".to_string(),
"shared.example.com".to_string()
])
);
assert_eq!(config.network.denied_domains(), None);
assert_eq!(config.denied_domains(), None);
}
#[test]
@@ -169,20 +169,20 @@ strip_request_headers = ["x-api-key"]
.expect("higher layer should apply");
let config = accumulator.finish().expect("merged config should build");
assert_eq!(config.network.mode, codex_network_proxy::NetworkMode::Full);
assert!(config.network.mitm);
assert_eq!(config.mode, codex_network_proxy::NetworkMode::Full);
assert!(config.mitm);
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec![
"lower.example.com".to_string(),
"higher.example.com".to_string()
])
);
assert_eq!(config.network.mitm_hooks.len(), 1);
assert_eq!(config.network.mitm_hooks[0].host, "api.github.com");
assert_eq!(config.network.mitm_hooks[0].matcher.methods, vec!["POST"]);
assert_eq!(config.mitm_hooks.len(), 1);
assert_eq!(config.mitm_hooks[0].host, "api.github.com");
assert_eq!(config.mitm_hooks[0].matcher.methods, vec!["POST"]);
assert_eq!(
config.network.mitm_hooks[0].actions.strip_request_headers,
config.mitm_hooks[0].actions.strip_request_headers,
vec!["x-api-key"]
);
}
@@ -190,12 +190,8 @@ strip_request_headers = ["x-api-key"]
#[test]
fn execpolicy_network_rules_overlay_network_lists() {
let mut config = NetworkProxyConfig::default();
config
.network
.set_allowed_domains(vec!["config.example.com".to_string()]);
config
.network
.set_denied_domains(vec!["blocked.example.com".to_string()]);
config.set_allowed_domains(vec!["config.example.com".to_string()]);
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
let mut exec_policy = Policy::empty();
exec_policy
@@ -218,14 +214,14 @@ fn execpolicy_network_rules_overlay_network_lists() {
apply_exec_policy_network_rules(&mut config, &exec_policy);
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec![
"config.example.com".to_string(),
"blocked.example.com".to_string()
])
);
assert_eq!(
config.network.denied_domains(),
config.denied_domains(),
Some(vec!["api.example.com".to_string()])
);
}
@@ -270,7 +266,7 @@ async fn malformed_custom_rules_preserve_managed_denied_domain() {
.expect("proxy state should tolerate malformed custom rules");
assert_eq!(
state.config.network.denied_domains(),
state.config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
}
@@ -468,7 +464,7 @@ fn config_from_layers_resolves_inherited_profiles_across_layers() {
config_from_layers(&layers, &Policy::empty()).expect("inherited profiles should load");
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec![
"base.example.com".to_string(),
"child.example.com".to_string(),
@@ -507,10 +503,10 @@ fn config_from_layers_normalizes_profile_network_domains_before_merging_layers()
.expect("network domain layer precedence should load");
assert_eq!(
config.network.allowed_domains(),
config.allowed_domains(),
Some(vec!["example.com".to_string()])
);
assert_eq!(config.network.denied_domains(), None);
assert_eq!(config.denied_domains(), None);
}
#[test]
@@ -542,8 +538,8 @@ fn config_from_layers_uses_only_the_final_selected_profile_network() {
let config = config_from_layers(&layers, &Policy::empty())
.expect("final built-in profile selection should load");
assert_eq!(config.network.allowed_domains(), None);
assert_eq!(config.network.denied_domains(), None);
assert_eq!(config.allowed_domains(), None);
assert_eq!(config.denied_domains(), None);
}
#[test]
+6 -18
View File
@@ -809,7 +809,7 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
let current_cfg = started_proxy.proxy().current_cfg().await?;
assert_eq!(
current_cfg.network.allowed_domains(),
current_cfg.allowed_domains(),
Some(vec!["example.com".to_string()])
);
Ok(())
@@ -854,7 +854,7 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
let current_cfg = started_proxy.proxy().current_cfg().await?;
assert_eq!(
current_cfg.network.allowed_domains(),
current_cfg.allowed_domains(),
Some(vec!["managed.example.com".to_string()])
);
Ok(())
@@ -895,7 +895,7 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R
let spec = spec.recompute_for_permission_profile(&PermissionProfile::workspace_write())?;
spec.apply_to_started_proxy(&started_proxy).await?;
let current_cfg = started_proxy.proxy().current_cfg().await?;
assert_eq!(current_cfg.network.allowed_domains(), None);
assert_eq!(current_cfg.allowed_domains(), None);
use tokio::io::AsyncReadExt as _;
use tokio::io::AsyncWriteExt as _;
@@ -934,9 +934,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
let initial_permission_profile = PermissionProfile::workspace_write();
let mut network_config = NetworkProxyConfig::default();
network_config
.network
.set_allowed_domains(vec!["evil.com".to_string()]);
network_config.set_allowed_domains(vec!["evil.com".to_string()]);
let requirements = NetworkConstraints {
domains: Some(NetworkDomainPermissionsToml {
entries: std::collections::BTreeMap::from([(
@@ -962,12 +960,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
)
.await?;
assert_eq!(
started_proxy
.proxy()
.current_cfg()
.await?
.network
.allowed_domains(),
started_proxy.proxy().current_cfg().await?.allowed_domains(),
Some(vec!["*.example.com".to_string(), "evil.com".to_string()])
);
@@ -1006,12 +999,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
.load_full()
.expect("managed network proxy should be present");
assert_eq!(
started_proxy
.proxy()
.current_cfg()
.await?
.network
.allowed_domains(),
started_proxy.proxy().current_cfg().await?.allowed_domains(),
Some(vec!["*.example.com".to_string()])
);
+1 -1
View File
@@ -637,7 +637,7 @@ impl Session {
let network_proxy_active = match network_proxy.as_ref() {
Some(started_network_proxy) => {
match started_network_proxy.proxy().current_cfg().await {
Ok(config) => config.network.enabled,
Ok(config) => config.enabled,
Err(err) => {
warn!(
"failed to read managed network proxy state for turn metrics: {err:#}"
@@ -1,6 +1,6 @@
use super::BindConnectionAttribution;
use super::write_attribution_frame;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use crate::runtime::network_proxy_state_for_policy;
use crate::state::NetworkProxyState;
use pretty_assertions::assert_eq;
@@ -28,9 +28,7 @@ fn attribution_frame_has_bounded_binary_prefix() -> io::Result<()> {
#[tokio::test]
async fn framed_connection_receives_registered_execution_state() -> Result<(), BoxError> {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
state.register_execution("token-1", "local", "execution-1");
let listener = TcpListener::bind("127.0.0.1:0").await?;
+49 -70
View File
@@ -15,19 +15,6 @@ use url::Url;
use crate::mitm_hook::MitmHookConfig;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct NetworkProxyConfig {
#[serde(default)]
pub network: NetworkProxySettings,
}
impl NetworkProxyConfig {
pub fn set_credential_broker_enabled(&mut self, enabled: bool) {
self.network.credential_broker = enabled;
self.network.mitm |= enabled;
}
}
/// Variant order encodes effective precedence for duplicate patterns:
/// `None < Allow < Deny`, so deny wins over allow when entries conflict.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@@ -125,7 +112,7 @@ pub struct NetworkUnixSocketPermissions {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct NetworkProxySettings {
pub struct NetworkProxyConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_proxy_url")]
@@ -156,7 +143,7 @@ pub struct NetworkProxySettings {
pub mitm_hooks: Vec<MitmHookConfig>,
}
impl Default for NetworkProxySettings {
impl Default for NetworkProxyConfig {
fn default() -> Self {
Self {
enabled: false,
@@ -179,7 +166,12 @@ impl Default for NetworkProxySettings {
}
}
impl NetworkProxySettings {
impl NetworkProxyConfig {
pub fn set_credential_broker_enabled(&mut self, enabled: bool) {
self.credential_broker = enabled;
self.mitm |= enabled;
}
pub fn allowed_domains(&self) -> Option<Vec<String>> {
self.domain_entries(NetworkDomainPermission::Allow)
}
@@ -340,7 +332,7 @@ fn clamp_non_loopback(
pub(crate) fn clamp_bind_addrs(
http_addr: SocketAddr,
socks_addr: SocketAddr,
cfg: &NetworkProxySettings,
cfg: &NetworkProxyConfig,
) -> (SocketAddr, SocketAddr) {
let http_addr = clamp_non_loopback(
http_addr,
@@ -416,7 +408,7 @@ impl ValidatedUnixSocketPath {
}
pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) -> Result<()> {
for (index, socket_path) in cfg.network.allow_unix_sockets().iter().enumerate() {
for (index, socket_path) in cfg.allow_unix_sockets().iter().enumerate() {
ValidatedUnixSocketPath::parse(socket_path)
.with_context(|| format!("invalid network.allow_unix_sockets[{index}]"))?;
}
@@ -426,11 +418,11 @@ pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) ->
pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result<RuntimeConfig> {
validate_unix_socket_allowlist_paths(cfg)?;
let http_addr = resolve_addr(&cfg.network.proxy_url, /*default_port*/ 3128)
.with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?;
let socks_addr = resolve_addr(&cfg.network.socks_url, /*default_port*/ 8081)
.with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?;
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg.network);
let http_addr = resolve_addr(&cfg.proxy_url, /*default_port*/ 3128)
.with_context(|| format!("invalid network.proxy_url: {}", cfg.proxy_url))?;
let socks_addr = resolve_addr(&cfg.socks_url, /*default_port*/ 8081)
.with_context(|| format!("invalid network.socks_url: {}", cfg.socks_url))?;
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, cfg);
Ok(RuntimeConfig {
http_addr,
@@ -575,8 +567,8 @@ mod tests {
use pretty_assertions::assert_eq;
fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxySettings {
let mut settings = NetworkProxySettings::default();
fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxyConfig {
let mut settings = NetworkProxyConfig::default();
if !unix_sockets.is_empty() {
settings.set_allow_unix_sockets(
unix_sockets
@@ -591,8 +583,8 @@ mod tests {
#[test]
fn network_proxy_settings_default_matches_local_use_baseline() {
assert_eq!(
NetworkProxySettings::default(),
NetworkProxySettings {
NetworkProxyConfig::default(),
NetworkProxyConfig {
enabled: false,
proxy_url: "http://127.0.0.1:3128".to_string(),
enable_socks5: true,
@@ -614,26 +606,19 @@ mod tests {
}
#[test]
fn partial_network_config_uses_struct_defaults_for_missing_fields() {
let config: NetworkProxyConfig = serde_json::from_str(
r#"{
"network": {
"enabled": true
}
}"#,
)
.unwrap();
let expected = NetworkProxySettings {
fn network_proxy_config_uses_struct_defaults_for_missing_fields() {
let config: NetworkProxyConfig = serde_json::from_str(r#"{ "enabled": true }"#).unwrap();
let expected = NetworkProxyConfig {
enabled: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
assert_eq!(config.network, expected);
assert_eq!(config, expected);
}
#[test]
fn set_allowed_domains_preserves_existing_deny_for_same_pattern() {
let mut settings = NetworkProxySettings::default();
let mut settings = NetworkProxyConfig::default();
settings.set_denied_domains(vec!["example.com".to_string()]);
settings.set_allowed_domains(vec!["example.com".to_string()]);
@@ -647,36 +632,34 @@ mod tests {
#[test]
fn network_domain_permissions_serialize_to_effective_map_shape() {
let mut settings = NetworkProxySettings::default();
let mut settings = NetworkProxyConfig::default();
settings.set_denied_domains(vec!["example.com".to_string()]);
settings.set_allowed_domains(vec!["example.com".to_string()]);
let config = NetworkProxyConfig { network: settings };
let config = settings;
let value = serde_json::to_value(&config).unwrap();
assert_eq!(
value,
serde_json::json!({
"network": {
"enabled": false,
"proxy_url": "http://127.0.0.1:3128",
"enable_socks5": true,
"socks_url": "http://127.0.0.1:8081",
"enable_socks5_udp": true,
"allow_upstream_proxy": true,
"dangerously_allow_non_loopback_proxy": false,
"dangerously_allow_all_unix_sockets": false,
"mode": "full",
"domains": {
"example.com": "deny",
},
"unix_sockets": null,
"allow_local_binding": false,
"mitm": false,
"credential_broker": false,
"dangerously_allow_plaintext_credential_injection": false,
"mitm_hooks": [],
}
"enabled": false,
"proxy_url": "http://127.0.0.1:3128",
"enable_socks5": true,
"socks_url": "http://127.0.0.1:8081",
"enable_socks5_udp": true,
"allow_upstream_proxy": true,
"dangerously_allow_non_loopback_proxy": false,
"dangerously_allow_all_unix_sockets": false,
"mode": "full",
"domains": {
"example.com": "deny",
},
"unix_sockets": null,
"allow_local_binding": false,
"mitm": false,
"credential_broker": false,
"dangerously_allow_plaintext_credential_injection": false,
"mitm_hooks": [],
})
);
}
@@ -815,7 +798,7 @@ mod tests {
#[test]
fn clamp_bind_addrs_allows_non_loopback_when_enabled() {
let cfg = NetworkProxySettings {
let cfg = NetworkProxyConfig {
dangerously_allow_non_loopback_proxy: true,
..Default::default()
};
@@ -846,7 +829,7 @@ mod tests {
#[test]
fn clamp_bind_addrs_forces_loopback_when_all_unix_sockets_enabled() {
let cfg = NetworkProxySettings {
let cfg = NetworkProxyConfig {
dangerously_allow_non_loopback_proxy: true,
dangerously_allow_all_unix_sockets: true,
..Default::default()
@@ -862,9 +845,7 @@ mod tests {
#[test]
fn resolve_runtime_rejects_relative_allow_unix_sockets_entries() {
let cfg = NetworkProxyConfig {
network: settings_with_unix_sockets(&["relative.sock"]),
};
let cfg = settings_with_unix_sockets(&["relative.sock"]);
let err = match resolve_runtime(&cfg) {
Ok(runtime) => panic!(
@@ -881,9 +862,7 @@ mod tests {
#[test]
fn resolve_runtime_accepts_unix_style_absolute_allow_unix_sockets_entries() {
let cfg = NetworkProxyConfig {
network: settings_with_unix_sockets(&["/private/tmp/example.sock"]),
};
let cfg = settings_with_unix_sockets(&["/private/tmp/example.sock"]);
assert!(
resolve_runtime(&cfg).is_ok(),
+4 -4
View File
@@ -107,7 +107,7 @@ impl TargetPolicy {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use crate::state::network_proxy_state_for_policy;
use rama_net::address::HostWithPort;
use std::net::Ipv4Addr;
@@ -120,7 +120,7 @@ mod tests {
.expect("bind local listener");
let target = listener.local_addr().expect("local addr");
let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
NetworkProxyConfig::default(),
)));
let request: rama_tcp::client::Request =
@@ -142,9 +142,9 @@ mod tests {
.expect("bind local listener");
let target = listener.local_addr().expect("local addr");
let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(
NetworkProxySettings {
NetworkProxyConfig {
allow_local_binding: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
},
)));
+19 -27
View File
@@ -1087,7 +1087,7 @@ mod tests {
use super::*;
use crate::config::NetworkMode;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use crate::runtime::network_proxy_state_for_policy;
use pretty_assertions::assert_eq;
use rama_http::Method;
@@ -1106,7 +1106,7 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_blocks_in_limited_mode() {
let policy = {
let mut policy = NetworkProxySettings::default();
let mut policy = NetworkProxyConfig::default();
policy.set_allowed_domains(vec!["example.com".to_string()]);
policy
};
@@ -1136,9 +1136,9 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_allows_allowlisted_host_in_full_mode() {
let policy = {
let mut policy = NetworkProxySettings {
let mut policy = NetworkProxyConfig {
allow_local_binding: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
policy.set_allowed_domains(vec!["example.com".to_string()]);
policy
@@ -1163,9 +1163,7 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_passes_environment_id_to_decider() {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
let seen_environment_id = Arc::new(Mutex::new(None));
let decider: Arc<dyn NetworkPolicyDecider> = Arc::new({
let seen_environment_id = seen_environment_id.clone();
@@ -1202,10 +1200,10 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_defers_brokered_host_mitm_until_protocol_detection() {
let mut policy = NetworkProxySettings {
let mut policy = NetworkProxyConfig {
credential_broker: true,
mitm: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
policy.set_allowed_domains(vec!["github.com".to_string()]);
let state = Arc::new(network_proxy_state_for_policy(policy));
@@ -1235,10 +1233,10 @@ mod tests {
#[tokio::test]
async fn plaintext_credential_injection_requires_explicit_opt_in() {
let real_token = "ghp-real";
let mut disabled_network = NetworkProxySettings {
let mut disabled_network = NetworkProxyConfig {
credential_broker: true,
mitm: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
disabled_network.set_allowed_domains(vec!["api.github.com".to_string()]);
let disabled_state = Arc::new(network_proxy_state_for_policy(disabled_network));
@@ -1263,11 +1261,11 @@ mod tests {
Some(&HeaderValue::from_str(&format!("Bearer {dummy_token}")).unwrap())
);
let mut enabled_network = NetworkProxySettings {
let mut enabled_network = NetworkProxyConfig {
credential_broker: true,
dangerously_allow_plaintext_credential_injection: true,
mitm: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
enabled_network.set_allowed_domains(vec!["api.github.com".to_string()]);
let enabled_state = Arc::new(network_proxy_state_for_policy(enabled_network));
@@ -1295,7 +1293,7 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state() {
let mut policy = NetworkProxySettings {
let mut policy = NetworkProxyConfig {
mitm: true,
mitm_hooks: vec![crate::mitm_hook::MitmHookConfig {
host: "api.github.com".to_string(),
@@ -1352,10 +1350,10 @@ mod tests {
});
let state = Arc::new(network_proxy_state_for_policy({
let mut network = NetworkProxySettings {
let mut network = NetworkProxyConfig {
credential_broker: true,
mitm: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
network.set_allowed_domains(vec!["127.0.0.1".to_string()]);
network.allow_local_binding = true;
@@ -1416,9 +1414,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn http_plain_proxy_blocks_unix_socket_when_method_not_allowed() {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
state
.set_network_mode(NetworkMode::Limited)
.await
@@ -1447,9 +1443,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn http_plain_proxy_rejects_unix_socket_when_not_allowlisted() {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
let mut req = Request::builder()
.method(Method::GET)
@@ -1480,7 +1474,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn http_plain_proxy_attempts_allowed_unix_socket_proxy() {
let state = Arc::new(network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allow_unix_sockets(vec!["/tmp/test.sock".to_string()]);
network
}));
@@ -1504,7 +1498,7 @@ mod tests {
#[tokio::test]
async fn http_connect_accept_denies_denylisted_host() {
let policy = {
let mut policy = NetworkProxySettings::default();
let mut policy = NetworkProxyConfig::default();
policy.set_allowed_domains(vec!["**.openai.com".to_string()]);
policy.set_denied_domains(vec!["api.openai.com".to_string()]);
policy
@@ -1533,9 +1527,7 @@ mod tests {
#[tokio::test]
async fn http_plain_proxy_rejects_absolute_uri_host_header_mismatch() {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
let mut req = Request::builder()
.method(Method::GET)
.uri("http://raw.githubusercontent.com/openai/codex/main/README.md")
-1
View File
@@ -78,7 +78,6 @@ pub use runtime::NetworkProxyState;
pub use state::NetworkProxyAuditMetadata;
pub use state::NetworkProxyConstraintError;
pub use state::NetworkProxyConstraints;
pub use state::PartialNetworkConfig;
pub use state::PartialNetworkProxyConfig;
pub use state::build_config_state;
pub use state::validate_policy_against_constraints;
+24 -26
View File
@@ -169,12 +169,12 @@ pub enum HookEvaluation {
}
pub(crate) fn validate_mitm_hook_config(config: &NetworkProxyConfig) -> Result<()> {
let hooks = &config.network.mitm_hooks;
let hooks = &config.mitm_hooks;
if hooks.is_empty() {
return Ok(());
}
if !config.network.mitm {
if !config.mitm {
return Err(anyhow!("network.mitm_hooks requires network.mitm = true"));
}
@@ -274,7 +274,7 @@ where
validate_mitm_hook_config(config)?;
let mut hooks_by_host = MitmHooksByHost::new();
for hook in &config.network.mitm_hooks {
for hook in &config.mitm_hooks {
let host = normalize_hook_host(&hook.host)?;
let methods = normalize_methods(&hook.matcher.methods)?;
let path_prefixes = compile_path_matchers(&hook.matcher.path_prefixes)?;
@@ -644,7 +644,7 @@ fn parse_secret_file(path: &str) -> Result<AbsolutePathBuf> {
mod tests {
use super::*;
use crate::NetworkMode;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use pretty_assertions::assert_eq;
use rama_http::Body;
use rama_http::Method;
@@ -652,11 +652,9 @@ mod tests {
fn base_config() -> NetworkProxyConfig {
NetworkProxyConfig {
network: NetworkProxySettings {
mitm: true,
mode: NetworkMode::Limited,
..NetworkProxySettings::default()
},
mitm: true,
mode: NetworkMode::Limited,
..NetworkProxyConfig::default()
}
}
@@ -683,8 +681,8 @@ mod tests {
#[test]
fn validate_requires_mitm_for_hooks() {
let mut config = base_config();
config.network.mitm = false;
config.network.mitm_hooks = vec![github_hook()];
config.mitm = false;
config.mitm_hooks = vec![github_hook()];
let err = validate_mitm_hook_config(&config).expect_err("hooks require mitm");
assert!(
@@ -696,8 +694,8 @@ mod tests {
#[test]
fn validate_allows_hooks_in_full_mode() {
let mut config = base_config();
config.network.mode = NetworkMode::Full;
config.network.mitm_hooks = vec![github_hook()];
config.mode = NetworkMode::Full;
config.mitm_hooks = vec![github_hook()];
validate_mitm_hook_config(&config).expect("hooks should be allowed in full mode");
}
@@ -709,7 +707,7 @@ mod tests {
hook.matcher.body = Some(MitmHookBodyConfig(serde_json::json!({
"repository": "openai/codex"
})));
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let err = validate_mitm_hook_config(&config).expect_err("body matchers are reserved");
assert!(err.to_string().contains("match.body is reserved"));
@@ -721,7 +719,7 @@ mod tests {
let mut hook = github_hook();
hook.actions.inject_request_headers[0].secret_env_var = None;
hook.actions.inject_request_headers[0].secret_file = Some("token.txt".to_string());
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let err = validate_mitm_hook_config(&config).expect_err("secret file must be absolute");
assert!(format!("{err:#}").contains("secret_file must be an absolute path"));
@@ -732,7 +730,7 @@ mod tests {
let mut config = base_config();
let mut hook = github_hook();
hook.actions.inject_request_headers[0].secret_file = Some("/tmp/github-token".to_string());
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let err = validate_mitm_hook_config(&config).expect_err("dual secret sources invalid");
assert!(format!("{err:#}").contains("exactly one of secret_env_var or secret_file"));
@@ -741,7 +739,7 @@ mod tests {
#[test]
fn compile_resolves_env_backed_injected_headers() {
let mut config = base_config();
config.network.mitm_hooks = vec![github_hook()];
config.mitm_hooks = vec![github_hook()];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -772,7 +770,7 @@ mod tests {
hook.actions.inject_request_headers[0].secret_env_var = None;
hook.actions.inject_request_headers[0].secret_file =
Some(secret_file.path().display().to_string());
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks(&config).unwrap();
let compiled = hooks.get("api.github.com").unwrap();
@@ -789,7 +787,7 @@ mod tests {
first.matcher.path_prefixes = vec!["/repos/openai/".to_string()];
let mut second = github_hook();
second.actions.inject_request_headers[0].prefix = Some("Token ".to_string());
config.network.mitm_hooks = vec![first, second];
config.mitm_hooks = vec![first, second];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -827,7 +825,7 @@ mod tests {
"x-github-api-version".to_string(),
vec!["2022-11-28".to_string()],
)]);
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -861,7 +859,7 @@ mod tests {
"x-github-api-version".to_string(),
vec!["pattern:2022*preview".to_string()],
)]);
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -889,7 +887,7 @@ mod tests {
let mut config = base_config();
let mut hook = github_hook();
hook.matcher.path_prefixes = vec!["pattern:/repos/[".to_string()];
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let err = validate_mitm_hook_config(&config).expect_err("invalid glob should fail");
assert!(format!("{err:#}").contains("invalid glob pattern"));
@@ -900,7 +898,7 @@ mod tests {
let mut config = base_config();
let mut hook = github_hook();
hook.matcher.path_prefixes = vec!["pattern:/repos/*/codex/issues*".to_string()];
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -930,7 +928,7 @@ mod tests {
"x-github-api-version".to_string(),
vec!["2022-11-28[preview]".to_string()],
)]);
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -973,7 +971,7 @@ mod tests {
"x-github-api-version".to_string(),
vec!["literal:pattern:*".to_string()],
)]);
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
@@ -1011,7 +1009,7 @@ mod tests {
let mut config = base_config();
let mut hook = github_hook();
hook.matcher.query = BTreeMap::from([("state".to_string(), vec!["open".to_string()])]);
config.network.mitm_hooks = vec![hook];
config.mitm_hooks = vec![hook];
let hooks = compile_mitm_hooks_with_resolvers(
&config,
+10 -10
View File
@@ -1,6 +1,6 @@
use super::*;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use crate::reasons::REASON_METHOD_NOT_ALLOWED;
use crate::reasons::REASON_MITM_HOOK_DENIED;
use crate::reasons::REASON_NOT_ALLOWED_LOCAL;
@@ -139,7 +139,7 @@ fn policy_ctx(
#[tokio::test]
async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() {
let app_state = Arc::new(network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network
}));
@@ -178,7 +178,7 @@ async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() {
#[tokio::test]
async fn mitm_policy_rejects_host_mismatch() {
let app_state = Arc::new(network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network
}));
@@ -207,7 +207,7 @@ async fn mitm_policy_rejects_host_mismatch() {
#[tokio::test]
async fn mitm_policy_rechecks_local_private_target_after_connect() {
let app_state = Arc::new(network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network.allow_local_binding = false;
network
@@ -247,11 +247,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() {
hook.actions.inject_request_headers[0].secret_env_var = None;
hook.actions.inject_request_headers[0].secret_file =
Some(secret_file.path().display().to_string());
let mut network = NetworkProxySettings {
let mut network = NetworkProxyConfig {
mitm: true,
mitm_hooks: vec![hook],
mode: NetworkMode::Full,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
network.set_allowed_domains(vec!["api.github.com".to_string()]);
let app_state = Arc::new(network_proxy_state_for_policy(network));
@@ -281,11 +281,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() {
async fn mitm_policy_blocks_matching_hooked_write_in_limited_mode() {
let mut hook = github_write_hook();
hook.actions.inject_request_headers.clear();
let mut network = NetworkProxySettings {
let mut network = NetworkProxyConfig {
mitm: true,
mitm_hooks: vec![hook],
mode: NetworkMode::Limited,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
network.set_allowed_domains(vec!["api.github.com".to_string()]);
let app_state = Arc::new(network_proxy_state_for_policy(network));
@@ -329,11 +329,11 @@ async fn mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_f
hook.actions.inject_request_headers[0].secret_env_var = None;
hook.actions.inject_request_headers[0].secret_file =
Some(secret_file.path().display().to_string());
let mut network = NetworkProxySettings {
let mut network = NetworkProxyConfig {
mitm: true,
mitm_hooks: vec![hook],
mode: NetworkMode::Full,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
network.set_allowed_domains(vec!["api.github.com".to_string()]);
let app_state = Arc::new(network_proxy_state_for_policy(network));
+9 -10
View File
@@ -554,7 +554,6 @@ mod tests {
use super::*;
use crate::config::NetworkMode;
use crate::config::NetworkProxyConfig;
use crate::config::NetworkProxySettings;
use crate::reasons::REASON_DENIED;
use crate::reasons::REASON_METHOD_NOT_ALLOWED;
use crate::reasons::REASON_NOT_ALLOWED;
@@ -595,12 +594,12 @@ mod tests {
}
fn state_with_metadata(metadata: NetworkProxyAuditMetadata) -> NetworkProxyState {
let network = NetworkProxySettings {
let network = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Full,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
let config = NetworkProxyConfig { network };
let config = network;
let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap();
let reloader = Arc::new(StaticReloader {
state: state.clone(),
@@ -628,7 +627,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn evaluate_host_policy_emits_domain_event_for_decider_allow_override() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
let calls = Arc::new(AtomicUsize::new(0));
let decider: Arc<dyn NetworkPolicyDecider> = Arc::new({
let calls = calls.clone();
@@ -697,7 +696,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn evaluate_host_policy_emits_execution_id_for_baseline_allow() {
let state = network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network
});
@@ -737,7 +736,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn evaluate_host_policy_emits_domain_event_for_baseline_deny() {
let state = network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network.set_denied_domains(vec!["blocked.com".to_string()]);
network
@@ -789,7 +788,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn evaluate_host_policy_emits_domain_event_for_decider_ask() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
let decider: Arc<dyn NetworkPolicyDecider> =
Arc::new(|_req| async { NetworkDecision::ask(REASON_NOT_ALLOWED) });
let request = NetworkPolicyRequest::new(NetworkPolicyRequestArgs {
@@ -876,7 +875,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn emit_block_decision_audit_event_emits_non_domain_event() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
let (_, events) = capture_events(|| async {
emit_block_decision_audit_event(
@@ -925,7 +924,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn evaluate_host_policy_still_denies_not_allowed_local_without_decider_override() {
let state = network_proxy_state_for_policy({
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["example.com".to_string()]);
network.allow_local_binding = false;
network
+54 -63
View File
@@ -183,55 +183,48 @@ impl NetworkProxyBuilder {
.set_blocked_request_observer(self.blocked_request_observer.clone())
.await;
let current_cfg = state.current_cfg().await?;
let (requested_http_addr, requested_socks_addr, reserved_listeners) = if self
.managed_by_codex
{
let runtime = config::resolve_runtime(&current_cfg)?;
#[cfg(target_os = "windows")]
let (managed_http_addr, managed_socks_addr) = config::clamp_bind_addrs(
runtime.http_addr,
runtime.socks_addr,
&current_cfg.network,
);
#[cfg(target_os = "windows")]
let reserved = reserve_windows_managed_listeners(
managed_http_addr,
managed_socks_addr,
current_cfg.network.enable_socks5,
)
.context("reserve managed loopback proxy listeners")?;
#[cfg(not(target_os = "windows"))]
let reserved = reserve_loopback_ephemeral_listeners(current_cfg.network.enable_socks5)
let (requested_http_addr, requested_socks_addr, reserved_listeners) =
if self.managed_by_codex {
let runtime = config::resolve_runtime(&current_cfg)?;
#[cfg(target_os = "windows")]
let (managed_http_addr, managed_socks_addr) =
config::clamp_bind_addrs(runtime.http_addr, runtime.socks_addr, &current_cfg);
#[cfg(target_os = "windows")]
let reserved = reserve_windows_managed_listeners(
managed_http_addr,
managed_socks_addr,
current_cfg.enable_socks5,
)
.context("reserve managed loopback proxy listeners")?;
let http_addr = reserved.http_addr()?;
let socks_addr = reserved.socks_addr(runtime.socks_addr)?;
(
http_addr,
socks_addr,
Some(reserved.into_reserved_listeners()),
)
} else {
let runtime = config::resolve_runtime(&current_cfg)?;
(
self.http_addr.unwrap_or(runtime.http_addr),
self.socks_addr.unwrap_or(runtime.socks_addr),
None,
)
};
#[cfg(not(target_os = "windows"))]
let reserved = reserve_loopback_ephemeral_listeners(current_cfg.enable_socks5)
.context("reserve managed loopback proxy listeners")?;
let http_addr = reserved.http_addr()?;
let socks_addr = reserved.socks_addr(runtime.socks_addr)?;
(
http_addr,
socks_addr,
Some(reserved.into_reserved_listeners()),
)
} else {
let runtime = config::resolve_runtime(&current_cfg)?;
(
self.http_addr.unwrap_or(runtime.http_addr),
self.socks_addr.unwrap_or(runtime.socks_addr),
None,
)
};
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
let (http_addr, socks_addr) = config::clamp_bind_addrs(
requested_http_addr,
requested_socks_addr,
&current_cfg.network,
);
let (http_addr, socks_addr) =
config::clamp_bind_addrs(requested_http_addr, requested_socks_addr, &current_cfg);
Ok(NetworkProxy {
state,
http_addr,
socks_addr,
socks_enabled: current_cfg.network.enable_socks5,
socks5_udp_enabled: current_cfg.network.enable_socks5_udp,
socks_enabled: current_cfg.enable_socks5,
socks5_udp_enabled: current_cfg.enable_socks5_udp,
runtime_settings: Arc::new(RwLock::new(NetworkProxyRuntimeSettings::from_config(
&current_cfg,
)?)),
@@ -317,16 +310,16 @@ struct NetworkProxyRuntimeSettings {
impl NetworkProxyRuntimeSettings {
fn from_config(config: &config::NetworkProxyConfig) -> Result<Self> {
let mitm_ca_trust_bundle = if config.network.mitm {
let mitm_ca_trust_bundle = if config.mitm {
let env = crate::certs::ca_env_from_process();
Some(crate::certs::managed_ca_trust_bundle(&env)?)
} else {
None
};
Ok(Self {
allow_local_binding: config.network.allow_local_binding,
allow_unix_sockets: config.network.allow_unix_sockets().into(),
dangerously_allow_all_unix_sockets: config.network.dangerously_allow_all_unix_sockets,
allow_local_binding: config.allow_local_binding,
allow_unix_sockets: config.allow_unix_sockets().into(),
dangerously_allow_all_unix_sockets: config.dangerously_allow_all_unix_sockets,
mitm_ca_trust_bundle,
})
}
@@ -879,23 +872,23 @@ impl NetworkProxy {
pub async fn replace_config_state(&self, new_state: ConfigState) -> Result<()> {
let current_cfg = self.state.current_cfg().await?;
anyhow::ensure!(
new_state.config.network.enabled == current_cfg.network.enabled,
new_state.config.enabled == current_cfg.enabled,
"cannot update network.enabled on a running proxy"
);
anyhow::ensure!(
new_state.config.network.proxy_url == current_cfg.network.proxy_url,
new_state.config.proxy_url == current_cfg.proxy_url,
"cannot update network.proxy_url on a running proxy"
);
anyhow::ensure!(
new_state.config.network.socks_url == current_cfg.network.socks_url,
new_state.config.socks_url == current_cfg.socks_url,
"cannot update network.socks_url on a running proxy"
);
anyhow::ensure!(
new_state.config.network.enable_socks5 == current_cfg.network.enable_socks5,
new_state.config.enable_socks5 == current_cfg.enable_socks5,
"cannot update network.enable_socks5 on a running proxy"
);
anyhow::ensure!(
new_state.config.network.enable_socks5_udp == current_cfg.network.enable_socks5_udp,
new_state.config.enable_socks5_udp == current_cfg.enable_socks5_udp,
"cannot update network.enable_socks5_udp on a running proxy"
);
@@ -922,7 +915,7 @@ impl NetworkProxy {
"execution-scoped network proxy is already running"
);
let current_cfg = self.state.current_cfg().await?;
if !current_cfg.network.enabled {
if !current_cfg.enabled {
warn!("network.enabled is false; skipping proxy listeners");
return Ok(NetworkProxyHandle::noop());
}
@@ -963,11 +956,11 @@ impl NetworkProxy {
}
});
let socks_task = if current_cfg.network.enable_socks5 {
let socks_task = if current_cfg.enable_socks5 {
let socks_state = self.state.clone();
let socks_decider = self.policy_decider.clone();
let socks_addr = self.socks_addr;
let enable_socks5_udp = current_cfg.network.enable_socks5_udp;
let enable_socks5_udp = current_cfg.enable_socks5_udp;
Some(tokio::spawn(async move {
match socks_listener {
Some(listener) => {
@@ -1095,7 +1088,7 @@ impl Drop for NetworkProxyHandle {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::NetworkProxySettings;
use crate::config::NetworkProxyConfig;
use crate::state::network_proxy_state_for_policy;
use pretty_assertions::assert_eq;
use std::net::IpAddr;
@@ -1111,10 +1104,10 @@ mod tests {
drop(http_listener);
drop(socks_listener);
let state = Arc::new(network_proxy_state_for_policy(NetworkProxySettings {
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig {
proxy_url: format!("http://{http_addr}"),
socks_url: format!("http://{socks_addr}"),
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
}));
let proxy = match NetworkProxy::builder().state(state).build().await {
Ok(proxy) => proxy,
@@ -1145,10 +1138,10 @@ mod tests {
#[tokio::test]
async fn non_codex_managed_proxy_builder_uses_configured_ports() {
let settings = NetworkProxySettings {
let settings = NetworkProxyConfig {
proxy_url: "http://127.0.0.1:43128".to_string(),
socks_url: "http://127.0.0.1:48081".to_string(),
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
let state = Arc::new(network_proxy_state_for_policy(settings));
let proxy = NetworkProxy::builder()
@@ -1170,9 +1163,7 @@ mod tests {
#[tokio::test]
async fn prepare_for_environment_keeps_env_and_sandbox_ports_in_sync() -> Result<()> {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
let proxy = NetworkProxy::builder().state(state).build().await?;
let handle = proxy.run().await?;
@@ -1229,11 +1220,11 @@ mod tests {
#[tokio::test]
async fn managed_proxy_builder_does_not_reserve_socks_listener_when_disabled() {
let settings = NetworkProxySettings {
let settings = NetworkProxyConfig {
enable_socks5: false,
proxy_url: "http://127.0.0.1:43128".to_string(),
socks_url: "http://127.0.0.1:43129".to_string(),
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
let state = Arc::new(network_proxy_state_for_policy(settings));
let proxy = match NetworkProxy::builder().state(state).build().await {
+90 -183
View File
@@ -298,7 +298,7 @@ impl NetworkProxyState {
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
) -> Self {
Self {
credential_broker: CredentialBroker::new(state.config.network.credential_broker),
credential_broker: CredentialBroker::new(state.config.credential_broker),
state: Arc::new(RwLock::new(state)),
reloader,
blocked_request_observer: Arc::new(RwLock::new(blocked_request_observer)),
@@ -381,7 +381,6 @@ impl NetworkProxyState {
let guard = self.state.read().await;
Ok(guard
.config
.network
.dangerously_allow_plaintext_credential_injection)
}
@@ -397,15 +396,15 @@ impl NetworkProxyState {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok((
guard.config.network.allowed_domains().unwrap_or_default(),
guard.config.network.denied_domains().unwrap_or_default(),
guard.config.allowed_domains().unwrap_or_default(),
guard.config.denied_domains().unwrap_or_default(),
))
}
pub async fn enabled(&self) -> Result<bool> {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok(guard.config.network.enabled)
Ok(guard.config.enabled)
}
pub async fn force_reload(&self) -> Result<()> {
@@ -457,11 +456,11 @@ impl NetworkProxyState {
};
let (deny_set, allow_set, allow_local_binding, allowed_domains) = {
let guard = self.state.read().await;
let allowed_domains = guard.config.network.allowed_domains();
let allowed_domains = guard.config.allowed_domains();
(
guard.deny_set.clone(),
guard.allow_set.clone(),
guard.config.network.allow_local_binding,
guard.config.allow_local_binding,
allowed_domains,
)
};
@@ -594,7 +593,7 @@ impl NetworkProxyState {
}
let guard = self.state.read().await;
if guard.config.network.dangerously_allow_all_unix_sockets {
if guard.config.dangerously_allow_all_unix_sockets {
return Ok(true);
}
@@ -604,7 +603,7 @@ impl NetworkProxyState {
Err(_) => return Ok(false),
};
let requested_canonical = std::fs::canonicalize(requested_abs.as_path()).ok();
for allowed in &guard.config.network.allow_unix_sockets() {
for allowed in &guard.config.allow_unix_sockets() {
let allowed_path = match ValidatedUnixSocketPath::parse(allowed) {
Ok(ValidatedUnixSocketPath::Native(path)) => path,
Ok(ValidatedUnixSocketPath::UnixStyleAbsolute(_)) => continue,
@@ -635,25 +634,25 @@ impl NetworkProxyState {
pub async fn method_allowed(&self, method: &str) -> Result<bool> {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok(guard.config.network.mode.allows_method(method))
Ok(guard.config.mode.allows_method(method))
}
pub async fn allow_upstream_proxy(&self) -> Result<bool> {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok(guard.config.network.allow_upstream_proxy)
Ok(guard.config.allow_upstream_proxy)
}
pub async fn allow_local_binding(&self) -> Result<bool> {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok(guard.config.network.allow_local_binding)
Ok(guard.config.allow_local_binding)
}
pub async fn network_mode(&self) -> Result<NetworkMode> {
self.reload_if_needed().await?;
let guard = self.state.read().await;
Ok(guard.config.network.mode)
Ok(guard.config.mode)
}
pub async fn set_network_mode(&self, mode: NetworkMode) -> Result<()> {
@@ -662,7 +661,7 @@ impl NetworkProxyState {
let (candidate, constraints) = {
let guard = self.state.read().await;
let mut candidate = guard.config.clone();
candidate.network.mode = mode;
candidate.mode = mode;
(candidate, guard.constraints.clone())
};
@@ -675,7 +674,7 @@ impl NetworkProxyState {
drop(guard);
continue;
}
guard.config.network.mode = mode;
guard.config.mode = mode;
info!("updated network mode to {mode:?}");
return Ok(());
}
@@ -740,8 +739,8 @@ impl NetworkProxyState {
};
let mut candidate = previous_cfg.clone();
let target_entries = target.entries(&candidate.network);
let opposite_entries = target.opposite_entries(&candidate.network);
let target_entries = target.entries(&candidate);
let opposite_entries = target.opposite_entries(&candidate);
let target_contains = target_entries
.iter()
.any(|entry| normalize_host(entry) == normalized_host);
@@ -752,7 +751,7 @@ impl NetworkProxyState {
return Ok(());
}
candidate.network.upsert_domain_permission(
candidate.upsert_domain_permission(
normalized_host.clone(),
target.permission(),
normalize_host,
@@ -809,7 +808,7 @@ impl NetworkProxyState {
fn ensure_credential_broker_enablement_unchanged(&self, new_state: &ConfigState) -> Result<()> {
anyhow::ensure!(
self.credential_broker.enabled() == new_state.config.network.credential_broker,
self.credential_broker.enabled() == new_state.config.credential_broker,
"network.credential_broker cannot change while the proxy is running"
);
Ok(())
@@ -844,14 +843,14 @@ impl DomainListKind {
}
}
fn entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String> {
fn entries(self, network: &crate::config::NetworkProxyConfig) -> Vec<String> {
match self {
Self::Allow => network.allowed_domains().unwrap_or_default(),
Self::Deny => network.denied_domains().unwrap_or_default(),
}
}
fn opposite_entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String> {
fn opposite_entries(self, network: &crate::config::NetworkProxyConfig) -> Vec<String> {
match self {
Self::Allow => network.denied_domains().unwrap_or_default(),
Self::Deny => network.allowed_domains().unwrap_or_default(),
@@ -905,15 +904,15 @@ where
}
fn log_policy_changes(previous: &NetworkProxyConfig, next: &NetworkProxyConfig) {
let previous_allowed_domains = previous.network.allowed_domains().unwrap_or_default();
let next_allowed_domains = next.network.allowed_domains().unwrap_or_default();
let previous_allowed_domains = previous.allowed_domains().unwrap_or_default();
let next_allowed_domains = next.allowed_domains().unwrap_or_default();
log_domain_list_changes(
"allowlist",
&previous_allowed_domains,
&next_allowed_domains,
);
let previous_denied_domains = previous.network.denied_domains().unwrap_or_default();
let next_denied_domains = next.network.denied_domains().unwrap_or_default();
let previous_denied_domains = previous.denied_domains().unwrap_or_default();
let next_denied_domains = next.denied_domains().unwrap_or_default();
log_domain_list_changes("denylist", &previous_denied_domains, &next_denied_domains);
}
@@ -980,13 +979,13 @@ fn unix_timestamp() -> i64 {
#[cfg(test)]
pub(crate) fn network_proxy_state_for_policy(
mut network: crate::config::NetworkProxySettings,
mut network: crate::config::NetworkProxyConfig,
) -> NetworkProxyState {
network.enabled = true;
let config = NetworkProxyConfig { network };
let config = network;
let state = ConfigState {
allow_set: crate::policy::compile_allowlist_globset(
&config.network.allowed_domains().unwrap_or_default(),
&config.allowed_domains().unwrap_or_default(),
)
.unwrap(),
blocked: VecDeque::new(),
@@ -994,7 +993,7 @@ pub(crate) fn network_proxy_state_for_policy(
config: config.clone(),
constraints: NetworkProxyConstraints::default(),
deny_set: crate::policy::compile_denylist_globset(
&config.network.denied_domains().unwrap_or_default(),
&config.denied_domains().unwrap_or_default(),
)
.unwrap(),
mitm: None,
@@ -1027,7 +1026,6 @@ mod tests {
use super::*;
use crate::config::NetworkProxyConfig;
use crate::config::NetworkProxySettings;
use crate::policy::compile_allowlist_globset;
use crate::policy::compile_denylist_globset;
use crate::state::NetworkProxyConstraints;
@@ -1060,8 +1058,8 @@ mod tests {
entries.iter().map(|entry| (*entry).to_string()).collect()
}
fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxySettings {
let mut network = NetworkProxySettings::default();
fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxyConfig {
let mut network = NetworkProxyConfig::default();
if !allowed_domains.is_empty() {
network.set_allowed_domains(strings(allowed_domains));
}
@@ -1075,7 +1073,7 @@ mod tests {
allowed_domains: &[&str],
denied_domains: &[&str],
unix_sockets: &[String],
) -> NetworkProxySettings {
) -> NetworkProxyConfig {
let mut network = network_settings(allowed_domains, denied_domains);
if !unix_sockets.is_empty() {
network.set_allow_unix_sockets(unix_sockets.to_vec());
@@ -1209,13 +1207,8 @@ mod tests {
#[tokio::test]
async fn add_allowed_domain_succeeds_when_managed_baseline_allows_expansion() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["managed.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["managed.example.com"], &[]);
config.enabled = true;
let constraints = NetworkProxyConstraints {
allowed_domains: Some(vec!["managed.example.com".to_string()]),
allowlist_expansion_enabled: Some(true),
@@ -1241,13 +1234,8 @@ mod tests {
#[tokio::test]
async fn add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["managed.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["managed.example.com"], &[]);
config.enabled = true;
let constraints = NetworkProxyConstraints {
allowed_domains: Some(vec!["managed.example.com".to_string()]),
allowlist_expansion_enabled: Some(false),
@@ -1271,13 +1259,8 @@ mod tests {
#[tokio::test]
async fn add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&[], &["managed.example.com"]);
network.enabled = true;
network
},
};
let mut config = network_settings(&[], &["managed.example.com"]);
config.enabled = true;
let constraints = NetworkProxyConstraints {
denied_domains: Some(vec!["managed.example.com".to_string()]),
denylist_expansion_enabled: Some(false),
@@ -1301,7 +1284,7 @@ mod tests {
#[tokio::test]
async fn blocked_snapshot_does_not_consume_entries() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
state
.record_blocked(BlockedRequest::new(BlockedRequestArgs {
@@ -1340,7 +1323,7 @@ mod tests {
#[tokio::test]
async fn drain_blocked_returns_buffered_window() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
for idx in 0..(MAX_BLOCKED_EVENTS + 5) {
state
@@ -1492,7 +1475,7 @@ mod tests {
#[tokio::test]
async fn host_blocked_requires_exact_scoped_ipv6_allowlist_match() {
let state = network_proxy_state_for_policy(NetworkProxySettings {
let state = network_proxy_state_for_policy(NetworkProxyConfig {
allow_local_binding: true,
..network_settings(&["fe80::1%eth0"], &[])
});
@@ -1515,7 +1498,7 @@ mod tests {
#[tokio::test]
async fn host_blocked_denies_scoped_ipv6_literal_before_local_binding() {
let state = network_proxy_state_for_policy(NetworkProxySettings {
let state = network_proxy_state_for_policy(NetworkProxyConfig {
allow_local_binding: true,
..network_settings(&["*"], &["fd00::1"])
});
@@ -1531,7 +1514,7 @@ mod tests {
#[tokio::test]
async fn host_blocked_requires_exact_scoped_ipv6_denylist_match() {
let state = network_proxy_state_for_policy(NetworkProxySettings {
let state = network_proxy_state_for_policy(NetworkProxyConfig {
allow_local_binding: true,
..network_settings(&["*"], &["fd00::1%eth0"])
});
@@ -1564,7 +1547,7 @@ mod tests {
#[tokio::test]
async fn host_blocked_rejects_loopback_when_allowlist_empty() {
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
assert_eq!(
state.host_blocked("127.0.0.1", /*port*/ 80).await.unwrap(),
@@ -1574,7 +1557,7 @@ mod tests {
#[tokio::test]
async fn host_blocked_rejects_allowlisted_hostname_when_dns_lookup_fails() {
let mut network = NetworkProxySettings::default();
let mut network = NetworkProxyConfig::default();
network.set_allowed_domains(vec!["does-not-resolve.invalid".to_string()]);
let state = network_proxy_state_for_policy(network);
@@ -1653,13 +1636,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["example.com", "evil.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["example.com", "evil.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1672,13 +1650,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["example.com", "api.openai.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["example.com", "api.openai.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
}
@@ -1691,11 +1664,9 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
mode: NetworkMode::Full,
..NetworkProxySettings::default()
},
enabled: true,
mode: NetworkMode::Full,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1708,13 +1679,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["api.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["api.example.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
}
@@ -1726,13 +1692,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["**.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["**.example.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1744,13 +1705,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["api.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["api.example.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1763,13 +1719,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["api.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["api.example.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1782,13 +1733,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["api.example.com"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["api.example.com"], &[]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1801,10 +1747,8 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
..NetworkProxySettings::default()
},
enabled: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1818,13 +1762,8 @@ mod tests {
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&[], &["evil.com", "more-evil.com"]);
network.enabled = true;
network
},
};
let mut config = network_settings(&[], &["evil.com", "more-evil.com"]);
config.enabled = true;
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
@@ -1837,10 +1776,8 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
..NetworkProxySettings::default()
},
enabled: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1854,11 +1791,9 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
allow_local_binding: true,
..NetworkProxySettings::default()
},
enabled: true,
allow_local_binding: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1873,11 +1808,9 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxySettings::default()
},
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1892,11 +1825,9 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxySettings::default()
},
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
@@ -1910,11 +1841,9 @@ mod tests {
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxySettings::default()
},
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
@@ -1925,11 +1854,9 @@ mod tests {
let constraints = NetworkProxyConstraints::default();
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxySettings::default()
},
enabled: true,
dangerously_allow_all_unix_sockets: true,
..NetworkProxyConfig::default()
};
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
@@ -2005,52 +1932,32 @@ mod tests {
#[test]
fn build_config_state_allows_global_wildcard_allowed_domains() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["*"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["*"], &[]);
config.enabled = true;
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok());
}
#[test]
fn build_config_state_allows_bracketed_global_wildcard_allowed_domains() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["[*]"], &[]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["[*]"], &[]);
config.enabled = true;
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok());
}
#[test]
fn build_config_state_rejects_global_wildcard_denied_domains() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["example.com"], &["*"]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["example.com"], &["*"]);
config.enabled = true;
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err());
}
#[test]
fn build_config_state_rejects_bracketed_global_wildcard_denied_domains() {
let config = NetworkProxyConfig {
network: {
let mut network = network_settings(&["example.com"], &["[*]"]);
network.enabled = true;
network
},
};
let mut config = network_settings(&["example.com"], &["[*]"]);
config.enabled = true;
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err());
}
+19 -23
View File
@@ -809,7 +809,6 @@ mod tests {
use super::*;
use crate::config::NetworkMode;
use crate::config::NetworkProxyConfig;
use crate::config::NetworkProxySettings;
use crate::mitm_hook::MitmHookConfig;
use crate::mitm_hook::MitmHookMatchConfig;
use crate::network_policy::test_support::POLICY_DECISION_EVENT_NAME;
@@ -855,12 +854,9 @@ mod tests {
}
}
fn state_for_settings(network: NetworkProxySettings) -> Arc<NetworkProxyState> {
let config = NetworkProxyConfig { network };
let _mitm_config_state_guard = config
.network
.mitm
.then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap());
fn state_for_settings(network: NetworkProxyConfig) -> Arc<NetworkProxyState> {
let config = network;
let _mitm_config_state_guard = config.mitm.then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap());
let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap();
let reloader = Arc::new(StaticReloader {
state: state.clone(),
@@ -870,10 +866,10 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_emits_block_decision_for_proxy_disabled() {
let state = state_for_settings(NetworkProxySettings {
let state = state_for_settings(NetworkProxyConfig {
enabled: false,
mode: NetworkMode::Full,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
});
let mut request =
TcpRequest::new(HostWithPort::try_from("example.com:443").expect("valid authority"));
@@ -912,11 +908,11 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_uses_mitm_in_limited_mode() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Limited,
mitm: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["example.com".to_string()]);
let state = state_for_settings(settings);
@@ -938,10 +934,10 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_blocks_non_https_in_limited_mode() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Limited,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["example.com".to_string()]);
let state = state_for_settings(settings);
@@ -985,12 +981,12 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_detects_tls_for_brokered_nonstandard_port_in_full_mode() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Full,
mitm: true,
credential_broker: true,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["api.openai.com".to_string()]);
let state = state_for_settings(settings);
@@ -1015,10 +1011,10 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_blocks_limited_mode_without_mitm_state() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Limited,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["example.com".to_string()]);
let state = state_for_settings(settings);
@@ -1043,7 +1039,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_uses_mitm_for_hooked_host_in_full_mode() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Full,
mitm: true,
@@ -1056,7 +1052,7 @@ mod tests {
},
..MitmHookConfig::default()
}],
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["api.github.com".to_string()]);
let state = state_for_settings(settings);
@@ -1078,7 +1074,7 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn handle_socks5_tcp_blocks_hooked_non_https_host_in_full_mode() {
let mut settings = NetworkProxySettings {
let mut settings = NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Full,
mitm: true,
@@ -1091,7 +1087,7 @@ mod tests {
},
..MitmHookConfig::default()
}],
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
};
settings.set_allowed_domains(vec!["api.github.com".to_string()]);
let state = state_for_settings(settings);
@@ -1116,10 +1112,10 @@ mod tests {
#[tokio::test(flavor = "current_thread")]
async fn inspect_socks5_udp_emits_block_decision_for_mode_guard_deny() {
let state = state_for_settings(NetworkProxySettings {
let state = state_for_settings(NetworkProxyConfig {
enabled: true,
mode: NetworkMode::Limited,
..NetworkProxySettings::default()
..NetworkProxyConfig::default()
});
let request = RelayRequest {
direction: RelayDirection::South,
+15 -21
View File
@@ -40,12 +40,6 @@ pub struct NetworkProxyConstraints {
#[derive(Debug, Clone, Deserialize)]
pub struct PartialNetworkProxyConfig {
#[serde(default)]
pub network: PartialNetworkConfig,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct PartialNetworkConfig {
pub enabled: Option<bool>,
pub mode: Option<NetworkMode>,
pub allow_upstream_proxy: Option<bool>,
@@ -69,20 +63,20 @@ pub fn build_config_state(
) -> anyhow::Result<ConfigState> {
crate::config::validate_unix_socket_allowlist_paths(&config)?;
anyhow::ensure!(
!config.network.credential_broker || config.network.mitm,
!config.credential_broker || config.mitm,
"network.credential_broker requires network.mitm = true"
);
let allowed_domains = config.network.allowed_domains().unwrap_or_default();
let denied_domains = config.network.denied_domains().unwrap_or_default();
let allowed_domains = config.allowed_domains().unwrap_or_default();
let denied_domains = config.denied_domains().unwrap_or_default();
validate_non_global_wildcard_domain_patterns("network.denied_domains", &denied_domains)
.map_err(NetworkProxyConstraintError::into_anyhow)?;
let deny_set = compile_denylist_globset(&denied_domains)?;
let allow_set = compile_allowlist_globset(&allowed_domains)?;
let mitm_hooks = compile_mitm_hooks(&config)?;
let mitm = if config.network.mitm {
let mitm = if config.mitm {
Some(Arc::new(MitmState::new(MitmUpstreamConfig {
allow_upstream_proxy: config.network.allow_upstream_proxy,
allow_local_binding: config.network.allow_local_binding,
allow_upstream_proxy: config.allow_upstream_proxy,
allow_local_binding: config.allow_local_binding,
})?))
} else {
None
@@ -122,14 +116,14 @@ pub fn validate_policy_against_constraints(
validator(&candidate)
}
let enabled = config.network.enabled;
let config_allowed_domains = config.network.allowed_domains().unwrap_or_default();
let config_denied_domains = config.network.denied_domains().unwrap_or_default();
let enabled = config.enabled;
let config_allowed_domains = config.allowed_domains().unwrap_or_default();
let config_denied_domains = config.denied_domains().unwrap_or_default();
let denied_domain_overrides: HashSet<String> = config_denied_domains
.iter()
.map(|entry| entry.to_ascii_lowercase())
.collect();
let config_allow_unix_sockets = config.network.allow_unix_sockets();
let config_allow_unix_sockets = config.allow_unix_sockets();
validate_mitm_hook_config(config).map_err(invalid_mitm_hook_configuration)?;
validate_non_global_wildcard_domain_patterns("network.denied_domains", &config_denied_domains)?;
if let Some(max_enabled) = constraints.enabled {
@@ -147,7 +141,7 @@ pub fn validate_policy_against_constraints(
}
if let Some(max_mode) = constraints.mode {
validate(config.network.mode, move |candidate| {
validate(config.mode, move |candidate| {
if network_mode_rank(*candidate) > network_mode_rank(max_mode) {
Err(invalid_value(
"network.mode",
@@ -162,7 +156,7 @@ pub fn validate_policy_against_constraints(
let allow_upstream_proxy = constraints.allow_upstream_proxy;
validate(
config.network.allow_upstream_proxy,
config.allow_upstream_proxy,
move |candidate| match allow_upstream_proxy {
Some(true) | None => Ok(()),
Some(false) => {
@@ -181,7 +175,7 @@ pub fn validate_policy_against_constraints(
let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy;
validate(
config.network.dangerously_allow_non_loopback_proxy,
config.dangerously_allow_non_loopback_proxy,
move |candidate| match allow_non_loopback_proxy {
Some(true) | None => Ok(()),
Some(false) => {
@@ -202,7 +196,7 @@ pub fn validate_policy_against_constraints(
.dangerously_allow_all_unix_sockets
.unwrap_or(constraints.allow_unix_sockets.is_none());
validate(
config.network.dangerously_allow_all_unix_sockets,
config.dangerously_allow_all_unix_sockets,
move |candidate| {
if *candidate && !allow_all_unix_sockets {
Err(invalid_value(
@@ -217,7 +211,7 @@ pub fn validate_policy_against_constraints(
)?;
if let Some(allow_local_binding) = constraints.allow_local_binding {
validate(config.network.allow_local_binding, move |candidate| {
validate(config.allow_local_binding, move |candidate| {
if *candidate && !allow_local_binding {
Err(invalid_value(
"network.allow_local_binding",
+3 -5
View File
@@ -658,11 +658,9 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a
let network_socket = "/tmp/codex-proxy-use";
let explicit_socket = "/tmp/codex-browser-use";
let mut network_config = NetworkProxyConfig::default();
network_config.network.enabled = true;
network_config.network.mode = NetworkMode::Full;
network_config
.network
.set_allow_unix_sockets(vec![network_socket.to_string()]);
network_config.enabled = true;
network_config.mode = NetworkMode::Full;
network_config.set_allow_unix_sockets(vec![network_socket.to_string()]);
let state = build_config_state(network_config, NetworkProxyConstraints::default())?;
let network_proxy = NetworkProxy::builder()
.state(Arc::new(NetworkProxyState::with_reloader(