[codex] Add danger-full-access denylist-only network mode (#16946)

## Summary

This adds `experimental_network.danger_full_access_denylist_only` for
orgs that want yolo / danger-full-access sessions to keep full network
access while still enforcing centrally managed deny rules.

When the flag is true and the session sandbox is `danger-full-access`,
the network proxy starts with:

- domain allowlist set to `*`
- managed domain `deny` entries enforced
- upstream proxy use allowed
- all Unix sockets allowed
- local/private binding allowed

Caveat: the denylist is best effort only. In yolo / danger-full-access
mode, Codex or the model can use an allowed socket or other
local/private network path to bypass the proxy denylist, so this should
not be treated as a hard security boundary.

The flag is intentionally scoped to `SandboxPolicy::DangerFullAccess`.
Read-only and workspace-write modes keep the existing managed/user
allowlist, denylist, Unix socket, and local-binding behavior. This does
not enable the non-loopback proxy listener setting; that still requires
its own explicit config.

This also threads the new field through config requirements parsing,
app-server protocol/schema output, config API mapping, and the TUI debug
config output.

## How to use

Add the flag under `[experimental_network]` in the network policy config
that is delivered to Codex. The setting is not under `[permissions]`.

```toml
[experimental_network]
enabled = true
danger_full_access_denylist_only = true

[experimental_network.domains]
"blocked.example.com" = "deny"
"*.blocked.example.com" = "deny"
```

With that configuration, yolo / danger-full-access sessions get broad
network access except for the managed denied domains above. The denylist
remains a best-effort proxy policy because the session may still use
allowed sockets to bypass it. Other sandbox modes do not get the
wildcard domain allowlist or the socket/local-binding relaxations from
this flag.

## Verification

- `cargo test -p codex-config network_requirements`
- `cargo test -p codex-core network_proxy_spec`
- `cargo test -p codex-app-server map_requirements_toml_to_api`
- `cargo test -p codex-tui debug_config_output`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`
- `just fmt`
- `just fix -p codex-config -p codex-core -p codex-app-server-protocol
-p codex-app-server -p codex-tui`
- `just fix -p codex-core -p codex-config`
- `git diff --check`
- `cargo clean`
This commit is contained in:
viyatb-oai
2026-04-06 19:38:51 -07:00
committed by GitHub
parent 806e5f7c69
commit 9d13d29acd
11 changed files with 256 additions and 30 deletions

View File

@@ -237,6 +237,8 @@ pub struct NetworkRequirementsToml {
/// When true, only managed `allowed_domains` are respected while managed
/// network enforcement is active. User allowlist entries are ignored.
pub managed_allowed_domains_only: Option<bool>,
/// In danger-full-access mode, allow all network access and enforce managed deny entries.
pub danger_full_access_denylist_only: Option<bool>,
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
pub allow_local_binding: Option<bool>,
}
@@ -255,6 +257,8 @@ struct RawNetworkRequirementsToml {
/// When true, only managed `allowed_domains` are respected while managed
/// network enforcement is active. User allowlist entries are ignored.
managed_allowed_domains_only: Option<bool>,
/// In danger-full-access mode, allow all network access and enforce managed deny entries.
danger_full_access_denylist_only: Option<bool>,
#[serde(default)]
denied_domains: Option<Vec<String>>,
unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
@@ -279,6 +283,7 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
domains,
allowed_domains,
managed_allowed_domains_only,
danger_full_access_denylist_only,
denied_domains,
unix_sockets,
allow_unix_sockets,
@@ -307,6 +312,7 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml {
domains: domains
.or_else(|| legacy_domain_permissions_from_lists(allowed_domains, denied_domains)),
managed_allowed_domains_only,
danger_full_access_denylist_only,
unix_sockets: unix_sockets
.or_else(|| legacy_unix_socket_permissions_from_list(allow_unix_sockets)),
allow_local_binding,
@@ -359,6 +365,8 @@ pub struct NetworkConstraints {
/// When true, only managed `allowed_domains` are respected while managed
/// network enforcement is active. User allowlist entries are ignored.
pub managed_allowed_domains_only: Option<bool>,
/// In danger-full-access mode, allow all network access and enforce managed deny entries.
pub danger_full_access_denylist_only: Option<bool>,
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
pub allow_local_binding: Option<bool>,
}
@@ -384,6 +392,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
dangerously_allow_all_unix_sockets,
domains,
managed_allowed_domains_only,
danger_full_access_denylist_only,
unix_sockets,
allow_local_binding,
} = value;
@@ -396,6 +405,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
dangerously_allow_all_unix_sockets,
domains,
managed_allowed_domains_only,
danger_full_access_denylist_only,
unix_sockets,
allow_local_binding,
}
@@ -1808,6 +1818,7 @@ allowed_approvals_reviewers = ["user"]
allow_upstream_proxy = false
dangerously_allow_all_unix_sockets = true
managed_allowed_domains_only = true
danger_full_access_denylist_only = true
allow_local_binding = false
[experimental_network.domains]
@@ -1858,6 +1869,10 @@ allowed_approvals_reviewers = ["user"]
sourced_network.value.managed_allowed_domains_only,
Some(true)
);
assert_eq!(
sourced_network.value.danger_full_access_denylist_only,
Some(true)
);
assert_eq!(
sourced_network.value.unix_sockets.as_ref(),
Some(&NetworkUnixSocketPermissionsToml {
@@ -1881,6 +1896,7 @@ allowed_approvals_reviewers = ["user"]
dangerously_allow_all_unix_sockets = true
allowed_domains = ["api.example.com", "*.openai.com"]
managed_allowed_domains_only = true
danger_full_access_denylist_only = true
denied_domains = ["blocked.example.com"]
allow_unix_sockets = ["/tmp/example.sock"]
allow_local_binding = false
@@ -1925,6 +1941,10 @@ allowed_approvals_reviewers = ["user"]
sourced_network.value.managed_allowed_domains_only,
Some(true)
);
assert_eq!(
sourced_network.value.danger_full_access_denylist_only,
Some(true)
);
assert_eq!(
sourced_network.value.unix_sockets.as_ref(),
Some(&NetworkUnixSocketPermissionsToml {