mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[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:
committed by
GitHub
Unverified
parent
806e5f7c69
commit
9d13d29acd
@@ -9552,6 +9552,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerFullAccessDenylistOnly": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowAllUnixSockets": {
|
||||
"type": [
|
||||
"boolean",
|
||||
|
||||
@@ -6375,6 +6375,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerFullAccessDenylistOnly": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowAllUnixSockets": {
|
||||
"type": [
|
||||
"boolean",
|
||||
|
||||
@@ -151,6 +151,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerFullAccessDenylistOnly": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowAllUnixSockets": {
|
||||
"type": [
|
||||
"boolean",
|
||||
|
||||
@@ -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, };
|
||||
|
||||
@@ -885,6 +885,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)]
|
||||
@@ -7820,6 +7821,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,
|
||||
@@ -7846,6 +7848,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([
|
||||
@@ -7876,6 +7879,7 @@ mod tests {
|
||||
"blocked.example.com": "deny"
|
||||
},
|
||||
"managedAllowedDomainsOnly": true,
|
||||
"dangerFullAccessDenylistOnly": true,
|
||||
"allowedDomains": ["api.openai.com"],
|
||||
"deniedDomains": ["blocked.example.com"],
|
||||
"unixSockets": {
|
||||
|
||||
@@ -196,7 +196,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
|
||||
|
||||
|
||||
@@ -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| {
|
||||
@@ -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(),
|
||||
@@ -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([(
|
||||
@@ -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(),
|
||||
@@ -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([(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use super::*;
|
||||
use crate::config_loader::NetworkDomainPermissionToml;
|
||||
use crate::config_loader::NetworkDomainPermissionsToml;
|
||||
use crate::config_loader::NetworkUnixSocketPermissionToml;
|
||||
use crate::config_loader::NetworkUnixSocketPermissionsToml;
|
||||
use codex_network_proxy::NetworkDomainPermission;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn domain_permissions(
|
||||
entries: impl IntoIterator<Item = (&'static str, NetworkDomainPermissionToml)>,
|
||||
@@ -178,6 +181,147 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn danger_full_access_denylist_only_allows_all_domains_and_enforces_managed_denies() {
|
||||
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()]);
|
||||
let requirements = NetworkConstraints {
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_all_unix_sockets: Some(false),
|
||||
domains: Some(domain_permissions([
|
||||
("*.example.com", NetworkDomainPermissionToml::Allow),
|
||||
("blocked.example.com", NetworkDomainPermissionToml::Deny),
|
||||
])),
|
||||
danger_full_access_denylist_only: Some(true),
|
||||
unix_sockets: Some(NetworkUnixSocketPermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"/tmp/managed.sock".to_string(),
|
||||
NetworkUnixSocketPermissionToml::Allow,
|
||||
)]),
|
||||
}),
|
||||
allow_local_binding: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(requirements),
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
)
|
||||
.expect("denylist-only yolo mode should allow all domains except managed denies");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
Some(vec!["*".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert!(spec.config.network.allow_upstream_proxy);
|
||||
assert!(spec.config.network.dangerously_allow_all_unix_sockets);
|
||||
assert!(spec.config.network.allow_local_binding);
|
||||
assert_eq!(spec.constraints.allow_upstream_proxy, Some(true));
|
||||
assert_eq!(
|
||||
spec.constraints.dangerously_allow_all_unix_sockets,
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(spec.constraints.allow_unix_sockets, None);
|
||||
assert_eq!(spec.constraints.allow_local_binding, Some(true));
|
||||
assert_eq!(spec.constraints.allowed_domains, None);
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, None);
|
||||
assert_eq!(
|
||||
spec.constraints.denied_domains,
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn danger_full_access_denylist_only_does_not_change_workspace_write_behavior() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_all_unix_sockets: Some(false),
|
||||
domains: Some(domain_permissions([
|
||||
("*.example.com", NetworkDomainPermissionToml::Allow),
|
||||
(
|
||||
"managed-blocked.example.com",
|
||||
NetworkDomainPermissionToml::Deny,
|
||||
),
|
||||
])),
|
||||
danger_full_access_denylist_only: Some(true),
|
||||
unix_sockets: Some(NetworkUnixSocketPermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"/tmp/managed.sock".to_string(),
|
||||
NetworkUnixSocketPermissionToml::Allow,
|
||||
)]),
|
||||
}),
|
||||
allow_local_binding: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
.expect("denylist-only yolo flag should not affect workspace-write mode");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
Some(vec![
|
||||
"*.example.com".to_string(),
|
||||
"api.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
Some(vec![
|
||||
"managed-blocked.example.com".to_string(),
|
||||
"blocked.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert!(!spec.config.network.allow_upstream_proxy);
|
||||
assert!(!spec.config.network.dangerously_allow_all_unix_sockets);
|
||||
assert_eq!(
|
||||
spec.config.network.allow_unix_sockets(),
|
||||
vec!["/tmp/managed.sock".to_string()]
|
||||
);
|
||||
assert!(!spec.config.network.allow_local_binding);
|
||||
assert_eq!(spec.constraints.allow_upstream_proxy, Some(false));
|
||||
assert_eq!(
|
||||
spec.constraints.dangerously_allow_all_unix_sockets,
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
spec.constraints.allow_unix_sockets,
|
||||
Some(vec!["/tmp/managed.sock".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.allow_local_binding, Some(false));
|
||||
assert_eq!(
|
||||
spec.constraints.allowed_domains,
|
||||
Some(vec!["*.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(true));
|
||||
assert_eq!(
|
||||
spec.constraints.denied_domains,
|
||||
Some(vec!["managed-blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
|
||||
@@ -337,6 +337,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
|
||||
dangerously_allow_all_unix_sockets,
|
||||
domains,
|
||||
managed_allowed_domains_only,
|
||||
danger_full_access_denylist_only,
|
||||
unix_sockets,
|
||||
allow_local_binding,
|
||||
} = network;
|
||||
@@ -374,6 +375,11 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
|
||||
"managed_allowed_domains_only={managed_allowed_domains_only}"
|
||||
));
|
||||
}
|
||||
if let Some(danger_full_access_denylist_only) = danger_full_access_denylist_only {
|
||||
parts.push(format!(
|
||||
"danger_full_access_denylist_only={danger_full_access_denylist_only}"
|
||||
));
|
||||
}
|
||||
if let Some(unix_sockets) = unix_sockets {
|
||||
parts.push(format!(
|
||||
"unix_sockets={}",
|
||||
@@ -557,6 +563,7 @@ mod tests {
|
||||
NetworkDomainPermissionToml::Allow,
|
||||
)]),
|
||||
}),
|
||||
danger_full_access_denylist_only: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::CloudRequirements,
|
||||
@@ -621,7 +628,7 @@ mod tests {
|
||||
assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))"));
|
||||
assert!(rendered.contains("enforce_residency: us (source: cloud requirements)"));
|
||||
assert!(rendered.contains(
|
||||
"experimental_network: enabled=true, domains={example.com=allow} (source: cloud requirements)"
|
||||
"experimental_network: enabled=true, domains={example.com=allow}, danger_full_access_denylist_only=true (source: cloud requirements)"
|
||||
));
|
||||
assert!(!rendered.contains(" - rules:"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user