mirror of
https://github.com/openai/codex.git
synced 2026-04-29 08:56:38 +00:00
fix: support managed network allowlist controls (#12752)
## Summary - treat `requirements.toml` `allowed_domains` and `denied_domains` as managed network baselines for the proxy - in restricted modes by default, build the effective runtime policy from the managed baseline plus user-configured allowlist and denylist entries, so common hosts can be pre-approved without blocking later user expansion - add `experimental_network.managed_allowed_domains_only = true` to pin the effective allowlist to managed entries, ignore user allowlist additions, and hard-deny non-managed domains without prompting - apply `managed_allowed_domains_only` anywhere managed network enforcement is active, including full access, while continuing to respect denied domains from all sources - add regression coverage for merged-baseline behavior, managed-only behavior, and full-access managed-only enforcement ## Behavior Assuming `requirements.toml` defines both `experimental_network.allowed_domains` and `experimental_network.denied_domains`. ### Default mode - By default, the effective allowlist is `experimental_network.allowed_domains` plus user or persisted allowlist additions. - By default, the effective denylist is `experimental_network.denied_domains` plus user or persisted denylist additions. - Allowlist misses can go through the network approval flow. - Explicit denylist hits and local or private-network blocks are still hard-denied. - When `experimental_network.managed_allowed_domains_only = true`, only managed `allowed_domains` are respected, user allowlist additions are ignored, and non-managed domains are hard-denied without prompting. - Denied domains continue to be respected from all sources. ### Full access - With managed requirements present, the effective allowlist is pinned to `experimental_network.allowed_domains`. - With managed requirements present, the effective denylist is pinned to `experimental_network.denied_domains`. - There is no allowlist-miss approval path in full access. - Explicit denylist hits are hard-denied. - `experimental_network.managed_allowed_domains_only = true` now also applies in full access, so managed-only behavior remains in effect anywhere managed network enforcement is active.
This commit is contained in:
@@ -894,6 +894,98 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_allowed_domain_succeeds_when_managed_baseline_allows_expansion() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
allowed_domains: vec!["managed.example.com".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
let constraints = NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(true),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
let state = NetworkProxyState::with_reloader(
|
||||
build_config_state(config, constraints).unwrap(),
|
||||
Arc::new(NoopReloader),
|
||||
);
|
||||
|
||||
state.add_allowed_domain("user.example.com").await.unwrap();
|
||||
|
||||
let (allowed, denied) = state.current_patterns().await.unwrap();
|
||||
assert_eq!(
|
||||
allowed,
|
||||
vec![
|
||||
"managed.example.com".to_string(),
|
||||
"user.example.com".to_string()
|
||||
]
|
||||
);
|
||||
assert!(denied.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
allowed_domains: vec!["managed.example.com".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
let constraints = NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(false),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
let state = NetworkProxyState::with_reloader(
|
||||
build_config_state(config, constraints).unwrap(),
|
||||
Arc::new(NoopReloader),
|
||||
);
|
||||
|
||||
let err = state
|
||||
.add_allowed_domain("user.example.com")
|
||||
.await
|
||||
.expect_err("managed baseline should reject allowlist expansion");
|
||||
|
||||
assert!(
|
||||
format!("{err:#}").contains("network.allowed_domains constrained by managed config"),
|
||||
"unexpected error: {err:#}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
denied_domains: vec!["managed.example.com".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
let constraints = NetworkProxyConstraints {
|
||||
denied_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
denylist_expansion_enabled: Some(false),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
let state = NetworkProxyState::with_reloader(
|
||||
build_config_state(config, constraints).unwrap(),
|
||||
Arc::new(NoopReloader),
|
||||
);
|
||||
|
||||
let err = state
|
||||
.add_denied_domain("user.example.com")
|
||||
.await
|
||||
.expect_err("managed baseline should reject denylist expansion");
|
||||
|
||||
assert!(
|
||||
format!("{err:#}").contains("network.denied_domains constrained by managed config"),
|
||||
"unexpected error: {err:#}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn blocked_snapshot_does_not_consume_entries() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
@@ -1117,6 +1209,25 @@ mod tests {
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_allows_expanding_allowed_domains_when_enabled() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(true),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
allowed_domains: vec!["example.com".to_string(), "api.openai.com".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_disallows_widening_mode() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
@@ -1245,6 +1356,25 @@ mod tests {
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_disallows_expanding_denied_domains_when_fixed() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
denied_domains: Some(vec!["evil.com".to_string()]),
|
||||
denylist_expansion_enabled: Some(false),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
denied_domains: vec!["evil.com".to_string(), "more-evil.com".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_disallows_enabling_when_managed_disabled() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
|
||||
Reference in New Issue
Block a user