Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9473,6 +9473,12 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6296,6 +6296,12 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ unixSockets: { [key in string]?: NetworkUnixSocketPermission } | null,
/**
* Legacy compatibility view derived from `unix_sockets`.
*/
allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, dangerFullAccessDenylistOnly: boolean | null, };
4 changes: 4 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ pub struct NetworkRequirements {
/// Legacy compatibility view derived from `unix_sockets`.
pub allow_unix_sockets: Option<Vec<String>>,
pub allow_local_binding: Option<bool>,
pub danger_full_access_denylist_only: Option<bool>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
Expand Down Expand Up @@ -7782,6 +7783,7 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
unix_sockets: None,
Expand All @@ -7808,6 +7810,7 @@ mod tests {
),
])),
managed_allowed_domains_only: Some(true),
danger_full_access_denylist_only: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
unix_sockets: Some(BTreeMap::from([
Expand Down Expand Up @@ -7838,6 +7841,7 @@ mod tests {
"blocked.example.com": "deny"
},
"managedAllowedDomainsOnly": true,
"dangerFullAccessDenylistOnly": true,
"allowedDomains": ["api.openai.com"],
"deniedDomains": ["blocked.example.com"],
"unixSockets": {
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Example with notification opt-out:
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home).
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.

### Example: Start or resume a thread

Expand Down
5 changes: 5 additions & 0 deletions codex-rs/app-server/src/config_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ fn map_network_requirements_to_api(
.collect()
}),
managed_allowed_domains_only: network.managed_allowed_domains_only,
danger_full_access_denylist_only: network.danger_full_access_denylist_only,
allowed_domains,
denied_domains,
unix_sockets: network.unix_sockets.map(|unix_sockets| {
Expand Down Expand Up @@ -594,6 +595,7 @@ mod tests {
]),
}),
managed_allowed_domains_only: Some(false),
danger_full_access_denylist_only: Some(true),
unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml {
entries: std::collections::BTreeMap::from([(
"/tmp/proxy.sock".to_string(),
Expand Down Expand Up @@ -653,6 +655,7 @@ mod tests {
("example.com".to_string(), NetworkDomainPermission::Deny),
])),
managed_allowed_domains_only: Some(false),
danger_full_access_denylist_only: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
unix_sockets: Some(std::collections::BTreeMap::from([(
Expand Down Expand Up @@ -687,6 +690,7 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml {
entries: std::collections::BTreeMap::from([(
"/tmp/ignored.sock".to_string(),
Expand All @@ -710,6 +714,7 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
allowed_domains: None,
denied_domains: None,
unix_sockets: Some(std::collections::BTreeMap::from([(
Expand Down
20 changes: 20 additions & 0 deletions codex-rs/config/src/config_requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
Expand All @@ -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>,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
}
Expand All @@ -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;
Expand All @@ -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,
}
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
82 changes: 55 additions & 27 deletions codex-rs/core/src/config/network_proxy_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use codex_protocol::protocol::SandboxPolicy;
use std::collections::HashSet;
use std::sync::Arc;

const GLOBAL_ALLOWLIST_PATTERN: &str = "*";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetworkProxySpec {
config: NetworkProxyConfig,
Expand Down Expand Up @@ -195,6 +197,8 @@ impl NetworkProxySpec {
let allowlist_expansion_enabled =
Self::allowlist_expansion_enabled(sandbox_policy, hard_deny_allowlist_misses);
let denylist_expansion_enabled = Self::denylist_expansion_enabled(sandbox_policy);
let danger_full_access_denylist_only =
Self::danger_full_access_denylist_only_enabled(requirements, sandbox_policy);

if let Some(enabled) = requirements.enabled {
config.network.enabled = enabled;
Expand Down Expand Up @@ -225,37 +229,43 @@ impl NetworkProxySpec {
constraints.dangerously_allow_all_unix_sockets =
Some(dangerously_allow_all_unix_sockets);
}
let managed_allowed_domains = if hard_deny_allowlist_misses {
Some(
if danger_full_access_denylist_only {
config
.network
.set_allowed_domains(vec![GLOBAL_ALLOWLIST_PATTERN.to_string()]);
} else {
let managed_allowed_domains = if hard_deny_allowlist_misses {
Some(
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
.unwrap_or_default(),
)
} else {
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
.unwrap_or_default(),
)
} else {
requirements
.domains
.as_ref()
.and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains)
};
if let Some(managed_allowed_domains) = managed_allowed_domains {
// Managed requirements seed the baseline allowlist. User additions
// can extend that baseline unless managed-only mode pins the
// effective allowlist to the managed set.
let effective_allowed_domains = if allowlist_expansion_enabled {
Self::merge_domain_lists(
managed_allowed_domains.clone(),
config.network.allowed_domains().as_deref().unwrap_or(&[]),
)
} else {
managed_allowed_domains.clone()
};
config
.network
.set_allowed_domains(effective_allowed_domains);
constraints.allowed_domains = Some(managed_allowed_domains);
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
if let Some(managed_allowed_domains) = managed_allowed_domains {
// Managed requirements seed the baseline allowlist. User additions
// can extend that baseline unless managed-only mode pins the
// effective allowlist to the managed set.
let effective_allowed_domains = if allowlist_expansion_enabled {
Self::merge_domain_lists(
managed_allowed_domains.clone(),
config.network.allowed_domains().as_deref().unwrap_or(&[]),
)
} else {
managed_allowed_domains.clone()
};
config
.network
.set_allowed_domains(effective_allowed_domains);
constraints.allowed_domains = Some(managed_allowed_domains);
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
}
}
let managed_denied_domains = requirements
.domains
Expand All @@ -274,7 +284,7 @@ impl NetworkProxySpec {
constraints.denied_domains = Some(managed_denied_domains);
constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled);
}
if requirements.unix_sockets.is_some() {
if requirements.unix_sockets.is_some() && !danger_full_access_denylist_only {
let allow_unix_sockets = requirements
.unix_sockets
.as_ref()
Expand All @@ -289,6 +299,14 @@ impl NetworkProxySpec {
config.network.allow_local_binding = allow_local_binding;
constraints.allow_local_binding = Some(allow_local_binding);
}
if danger_full_access_denylist_only {
config.network.allow_upstream_proxy = true;
constraints.allow_upstream_proxy = Some(true);
config.network.dangerously_allow_all_unix_sockets = true;
constraints.dangerously_allow_all_unix_sockets = Some(true);
config.network.allow_local_binding = true;
constraints.allow_local_binding = Some(true);
}

(config, constraints)
}
Expand All @@ -307,6 +325,16 @@ impl NetworkProxySpec {
requirements.managed_allowed_domains_only.unwrap_or(false)
}

fn danger_full_access_denylist_only_enabled(
requirements: &NetworkConstraints,
sandbox_policy: &SandboxPolicy,
) -> bool {
matches!(sandbox_policy, SandboxPolicy::DangerFullAccess)
&& requirements
.danger_full_access_denylist_only
.unwrap_or(false)
}

fn denylist_expansion_enabled(sandbox_policy: &SandboxPolicy) -> bool {
matches!(
sandbox_policy,
Expand Down
Loading
Loading