mirror of
https://github.com/openai/codex.git
synced 2026-04-28 08:34:54 +00:00
chore: refactor network permissions to use explicit domain and unix socket rule maps (#15120)
## Summary This PR replaces the legacy network allow/deny list model with explicit rule maps for domains and unix sockets across managed requirements, permissions profiles, the network proxy config, and the app server protocol. Concretely, it: - introduces typed domain (`allow` / `deny`) and unix socket permission (`allow` / `none`) entries instead of separate `allowed_domains`, `denied_domains`, and `allow_unix_sockets` lists - updates config loading, managed requirements merging, and exec-policy overlays to read and upsert rule entries consistently - exposes the new shape through protocol/schema outputs, debug surfaces, and app-server config APIs - rejects the legacy list-based keys and updates docs/tests to reflect the new config format ## Why The previous representation split related network policy across multiple parallel lists, which made merging and overriding rules harder to reason about. Moving to explicit keyed permission maps gives us a single source of truth per host/socket entry, makes allow/deny precedence clearer, and gives protocol consumers access to the full rule state instead of derived projections only. ## Backward Compatibility ### Backward compatible - Managed requirements still accept the legacy `experimental_network.allowed_domains`, `experimental_network.denied_domains`, and `experimental_network.allow_unix_sockets` fields. They are normalized into the new canonical `domains` and `unix_sockets` maps internally. - App-server v2 still deserializes legacy `allowedDomains`, `deniedDomains`, and `allowUnixSockets` payloads, so older clients can continue reading managed network requirements. - App-server v2 responses still populate `allowedDomains`, `deniedDomains`, and `allowUnixSockets` as legacy compatibility views derived from the canonical maps. - `managed_allowed_domains_only` keeps the same behavior after normalization. Legacy managed allowlists still participate in the same enforcement path as canonical `domains` entries. ### Not backward compatible - Permissions profiles under `[permissions.<profile>.network]` no longer accept the legacy list-based keys. Those configs must use the canonical `[domains]` and `[unix_sockets]` tables instead of `allowed_domains`, `denied_domains`, or `allow_unix_sockets`. - Managed `experimental_network` config cannot mix canonical and legacy forms in the same block. For example, `domains` cannot be combined with `allowed_domains` or `denied_domains`, and `unix_sockets` cannot be combined with `allow_unix_sockets`. - The canonical format can express explicit `"none"` entries for unix sockets, but those entries do not round-trip through the legacy compatibility fields because the legacy fields only represent allow/deny lists. ## Testing `/target/debug/codex sandbox macos --log-denials /bin/zsh -c 'curl https://www.example.com' ` gives 200 with config ``` [permissions.workspace.network.domains] "www.example.com" = "allow" ``` and fails when set to deny: `curl: (56) CONNECT tunnel failed, response 403`. Also tested backward compatibility path by verifying that adding the following to `/etc/codex/requirements.toml` works: ``` [experimental_network] allowed_domains = ["www.example.com"] ```
This commit is contained in:
@@ -1002,9 +1002,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_blocks_in_limited_mode() {
|
||||
let policy = NetworkProxySettings {
|
||||
allowed_domains: vec!["example.com".to_string()],
|
||||
..Default::default()
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings::default();
|
||||
policy.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
policy
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
state.set_network_mode(NetworkMode::Limited).await.unwrap();
|
||||
@@ -1027,9 +1028,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_allows_allowlisted_host_in_full_mode() {
|
||||
let policy = NetworkProxySettings {
|
||||
allowed_domains: vec!["example.com".to_string()],
|
||||
..Default::default()
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings::default();
|
||||
policy.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
policy
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
|
||||
@@ -1062,10 +1064,11 @@ mod tests {
|
||||
let _ = timeout(Duration::from_secs(1), stream.read(&mut buf)).await;
|
||||
});
|
||||
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxySettings {
|
||||
allowed_domains: vec!["127.0.0.1".to_string()],
|
||||
allow_local_binding: true,
|
||||
..NetworkProxySettings::default()
|
||||
let state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
network.set_allowed_domains(vec!["127.0.0.1".to_string()]);
|
||||
network.allow_local_binding = true;
|
||||
network
|
||||
}));
|
||||
let listener =
|
||||
StdTcpListener::bind((Ipv4Addr::LOCALHOST, 0)).expect("proxy listener should bind");
|
||||
@@ -1161,9 +1164,10 @@ mod tests {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn http_plain_proxy_attempts_allowed_unix_socket_proxy() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxySettings {
|
||||
allow_unix_sockets: vec!["/tmp/test.sock".to_string()],
|
||||
..NetworkProxySettings::default()
|
||||
let state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
network.set_allow_unix_sockets(vec!["/tmp/test.sock".to_string()]);
|
||||
network
|
||||
}));
|
||||
|
||||
let mut req = Request::builder()
|
||||
@@ -1180,10 +1184,11 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_denies_denylisted_host() {
|
||||
let policy = NetworkProxySettings {
|
||||
allowed_domains: vec!["**.openai.com".to_string()],
|
||||
denied_domains: vec!["api.openai.com".to_string()],
|
||||
..Default::default()
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings::default();
|
||||
policy.set_allowed_domains(vec!["**.openai.com".to_string()]);
|
||||
policy.set_denied_domains(vec!["api.openai.com".to_string()]);
|
||||
policy
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user