mirror of
https://github.com/openai/codex.git
synced 2026-04-23 06:04:53 +00:00
Compare commits
6 Commits
codex-debu
...
codex/viya
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
affe05fef2 | ||
|
|
0f407f3804 | ||
|
|
f3d1633b62 | ||
|
|
fcad780b17 | ||
|
|
577d7a95fd | ||
|
|
608635c4c4 |
@@ -927,6 +927,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"allow_unix_sockets": {
|
||||
"description": "When `requirements.toml` sets `experimental_network.allow_unix_sockets`, values here may further narrow access but must stay within the managed socket allowlist (subset semantics).",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -936,6 +937,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowed_domains": {
|
||||
"description": "When `requirements.toml` sets `experimental_network.allowed_domains`, values here may further narrow access but must stay within the managed allowlist (subset semantics).",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -948,6 +950,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"denied_domains": {
|
||||
"description": "When `requirements.toml` sets `experimental_network.denied_domains`, managed entries are always enforced. Values here can add more denied domains but cannot remove the managed ones (floor semantics).",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -448,6 +448,7 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
|
||||
let spec = crate::config::NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig::default(),
|
||||
None,
|
||||
None,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
let mut exec_policy = Policy::empty();
|
||||
@@ -482,6 +483,7 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
|
||||
{
|
||||
let spec = crate::config::NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig::default(),
|
||||
None,
|
||||
Some(NetworkConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
managed_allowed_domains_only: Some(true),
|
||||
|
||||
@@ -2176,6 +2176,7 @@ impl Config {
|
||||
&& has_permission_profiles);
|
||||
let (
|
||||
configured_network_proxy_config,
|
||||
configured_user_network,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -2195,6 +2196,7 @@ impl Config {
|
||||
let profile = resolve_permission_profile(permissions, default_permissions)?;
|
||||
let configured_network_proxy_config =
|
||||
network_proxy_config_from_profile_network(profile.network.as_ref());
|
||||
let configured_user_network = profile.network.as_ref();
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
compile_permission_profile(
|
||||
permissions,
|
||||
@@ -2214,12 +2216,14 @@ impl Config {
|
||||
}
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
configured_user_network,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
} else {
|
||||
let configured_network_proxy_config = NetworkProxyConfig::default();
|
||||
let configured_user_network = None;
|
||||
let mut sandbox_policy = cfg.derive_sandbox_policy(
|
||||
sandbox_mode,
|
||||
config_profile.sandbox_mode,
|
||||
@@ -2241,6 +2245,7 @@ impl Config {
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
configured_user_network,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -2543,6 +2548,7 @@ impl Config {
|
||||
let has_network_requirements = network_requirements.is_some();
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
configured_network_proxy_config,
|
||||
configured_user_network,
|
||||
network_requirements,
|
||||
constrained_sandbox_policy.get(),
|
||||
)
|
||||
|
||||
@@ -86,6 +86,7 @@ impl NetworkProxySpec {
|
||||
|
||||
pub(crate) fn from_config_and_constraints(
|
||||
config: NetworkProxyConfig,
|
||||
user_network: Option<&crate::config::permissions::NetworkToml>,
|
||||
requirements: Option<NetworkConstraints>,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
) -> std::io::Result<Self> {
|
||||
@@ -95,6 +96,7 @@ impl NetworkProxySpec {
|
||||
let (config, constraints) = if let Some(requirements) = requirements {
|
||||
Self::apply_requirements(
|
||||
config,
|
||||
user_network,
|
||||
&requirements,
|
||||
sandbox_policy,
|
||||
hard_deny_allowlist_misses,
|
||||
@@ -187,6 +189,7 @@ impl NetworkProxySpec {
|
||||
|
||||
fn apply_requirements(
|
||||
mut config: NetworkProxyConfig,
|
||||
user_network: Option<&crate::config::permissions::NetworkToml>,
|
||||
requirements: &NetworkConstraints,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
hard_deny_allowlist_misses: bool,
|
||||
@@ -197,7 +200,13 @@ impl NetworkProxySpec {
|
||||
let denylist_expansion_enabled = Self::denylist_expansion_enabled(sandbox_policy);
|
||||
|
||||
if let Some(enabled) = requirements.enabled {
|
||||
config.network.enabled = enabled;
|
||||
if enabled {
|
||||
if user_network.and_then(|network| network.enabled).is_none() {
|
||||
config.network.enabled = true;
|
||||
}
|
||||
} else {
|
||||
config.network.enabled = false;
|
||||
}
|
||||
constraints.enabled = Some(enabled);
|
||||
}
|
||||
if let Some(http_port) = requirements.http_port {
|
||||
@@ -207,14 +216,17 @@ impl NetworkProxySpec {
|
||||
config.network.socks_url = format!("http://127.0.0.1:{socks_port}");
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = requirements.allow_upstream_proxy {
|
||||
config.network.allow_upstream_proxy = allow_upstream_proxy;
|
||||
if !allow_upstream_proxy {
|
||||
config.network.allow_upstream_proxy = false;
|
||||
}
|
||||
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_proxy) =
|
||||
requirements.dangerously_allow_non_loopback_proxy
|
||||
{
|
||||
config.network.dangerously_allow_non_loopback_proxy =
|
||||
dangerously_allow_non_loopback_proxy;
|
||||
if !dangerously_allow_non_loopback_proxy {
|
||||
config.network.dangerously_allow_non_loopback_proxy = false;
|
||||
}
|
||||
constraints.dangerously_allow_non_loopback_proxy =
|
||||
Some(dangerously_allow_non_loopback_proxy);
|
||||
}
|
||||
@@ -231,32 +243,53 @@ impl NetworkProxySpec {
|
||||
requirements.allowed_domains.clone()
|
||||
};
|
||||
if let Some(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.
|
||||
config.network.allowed_domains = if allowlist_expansion_enabled {
|
||||
Self::merge_domain_lists(allowed_domains.clone(), &config.network.allowed_domains)
|
||||
if allowlist_expansion_enabled {
|
||||
// Seed the managed allowlist when the user left the field
|
||||
// unspecified. If the user configured an allowlist, keep that
|
||||
// narrower value and enforce the managed list as a subset
|
||||
// constraint during validation.
|
||||
if user_network
|
||||
.and_then(|network| network.allowed_domains.as_ref())
|
||||
.is_none()
|
||||
{
|
||||
config.network.allowed_domains = allowed_domains.clone();
|
||||
}
|
||||
} else {
|
||||
allowed_domains.clone()
|
||||
config.network.allowed_domains = allowed_domains.clone();
|
||||
};
|
||||
constraints.allowed_domains = Some(allowed_domains);
|
||||
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
|
||||
}
|
||||
if let Some(denied_domains) = requirements.denied_domains.clone() {
|
||||
config.network.denied_domains = if denylist_expansion_enabled {
|
||||
Self::merge_domain_lists(denied_domains.clone(), &config.network.denied_domains)
|
||||
if denylist_expansion_enabled {
|
||||
// Managed denied domains are a floor. Preserve user-added entries,
|
||||
// but ensure all managed entries are present.
|
||||
for denied_domain in &denied_domains {
|
||||
if !config
|
||||
.network
|
||||
.denied_domains
|
||||
.iter()
|
||||
.any(|existing| existing.eq_ignore_ascii_case(denied_domain))
|
||||
{
|
||||
config.network.denied_domains.push(denied_domain.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
denied_domains.clone()
|
||||
};
|
||||
config.network.denied_domains = denied_domains.clone();
|
||||
}
|
||||
constraints.denied_domains = Some(denied_domains);
|
||||
constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled);
|
||||
}
|
||||
if let Some(allow_unix_sockets) = requirements.allow_unix_sockets.clone() {
|
||||
config.network.allow_unix_sockets = allow_unix_sockets.clone();
|
||||
// Keep the user-configured socket allowlist if it is narrower.
|
||||
// Managed requirements are enforced as a subset constraint during
|
||||
// validation.
|
||||
constraints.allow_unix_sockets = Some(allow_unix_sockets);
|
||||
}
|
||||
if let Some(allow_local_binding) = requirements.allow_local_binding {
|
||||
config.network.allow_local_binding = allow_local_binding;
|
||||
if !allow_local_binding {
|
||||
config.network.allow_local_binding = false;
|
||||
}
|
||||
constraints.allow_local_binding = Some(allow_local_binding);
|
||||
}
|
||||
|
||||
@@ -283,18 +316,6 @@ impl NetworkProxySpec {
|
||||
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn merge_domain_lists(mut managed: Vec<String>, user_entries: &[String]) -> Vec<String> {
|
||||
for entry in user_entries {
|
||||
if !managed
|
||||
.iter()
|
||||
.any(|managed_entry| managed_entry.eq_ignore_ascii_case(entry))
|
||||
{
|
||||
managed.push(entry.clone());
|
||||
}
|
||||
}
|
||||
managed
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_exec_policy_network_rules(config: &mut NetworkProxyConfig, exec_policy: &Policy) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use codex_network_proxy::NetworkMode;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -25,6 +26,10 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() {
|
||||
fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["api.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
allowed_domains: Some(vec!["*.example.com".to_string()]),
|
||||
..Default::default()
|
||||
@@ -32,6 +37,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
@@ -39,7 +45,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains,
|
||||
vec!["*.example.com".to_string(), "api.example.com".to_string()]
|
||||
vec!["api.example.com".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
spec.constraints.allowed_domains,
|
||||
@@ -53,6 +59,11 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["evil.com".to_string()];
|
||||
config.network.denied_domains = vec!["more-blocked.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["evil.com".to_string()]),
|
||||
denied_domains: Some(vec!["more-blocked.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
allowed_domains: Some(vec!["*.example.com".to_string()]),
|
||||
denied_domains: Some(vec!["blocked.example.com".to_string()]),
|
||||
@@ -61,6 +72,7 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
)
|
||||
@@ -82,6 +94,10 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["api.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
allowed_domains: Some(vec!["*.example.com".to_string()]),
|
||||
managed_allowed_domains_only: Some(true),
|
||||
@@ -90,6 +106,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
@@ -106,6 +123,10 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["api.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
managed_allowed_domains_only: Some(true),
|
||||
@@ -114,6 +135,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
@@ -135,6 +157,10 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
|
||||
fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["api.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
managed_allowed_domains_only: Some(true),
|
||||
..Default::default()
|
||||
@@ -142,6 +168,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
@@ -157,6 +184,10 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
|
||||
fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_managed_list() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["api.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
managed_allowed_domains_only: Some(true),
|
||||
..Default::default()
|
||||
@@ -164,6 +195,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
)
|
||||
@@ -179,6 +211,10 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
|
||||
fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.denied_domains = vec!["blocked.example.com".to_string()];
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
denied_domains: Some(vec!["blocked.example.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = NetworkConstraints {
|
||||
denied_domains: Some(vec!["managed-blocked.example.com".to_string()]),
|
||||
..Default::default()
|
||||
@@ -186,6 +222,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
@@ -194,9 +231,225 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains,
|
||||
vec![
|
||||
"managed-blocked.example.com".to_string(),
|
||||
"blocked.example.com".to_string()
|
||||
"blocked.example.com".to_string(),
|
||||
"managed-blocked.example.com".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_requirements_preserve_more_restrictive_user_network_settings() -> std::io::Result<()> {
|
||||
let mut user_config = NetworkProxyConfig::default();
|
||||
user_config.network.enabled = false;
|
||||
user_config.network.mode = NetworkMode::Limited;
|
||||
user_config.network.allow_upstream_proxy = false;
|
||||
user_config.network.dangerously_allow_non_loopback_proxy = false;
|
||||
user_config.network.allowed_domains = vec!["api.openai.com".to_string()];
|
||||
user_config.network.denied_domains = vec!["tracker.com".to_string(), "evil.com".to_string()];
|
||||
user_config.network.allow_unix_sockets = vec!["/tmp/a.sock".to_string()];
|
||||
user_config.network.allow_local_binding = false;
|
||||
|
||||
let requirements = NetworkConstraints {
|
||||
enabled: Some(true),
|
||||
http_port: Some(43128),
|
||||
socks_port: Some(43129),
|
||||
allow_upstream_proxy: Some(true),
|
||||
dangerously_allow_non_loopback_proxy: Some(true),
|
||||
dangerously_allow_all_unix_sockets: None,
|
||||
allowed_domains: Some(vec!["*.openai.com".to_string()]),
|
||||
managed_allowed_domains_only: None,
|
||||
denied_domains: Some(vec!["evil.com".to_string()]),
|
||||
allow_unix_sockets: Some(vec!["/tmp/a.sock".to_string(), "/tmp/b.sock".to_string()]),
|
||||
allow_local_binding: Some(true),
|
||||
};
|
||||
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
enabled: Some(false),
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: Some(false),
|
||||
mode: Some(NetworkMode::Limited),
|
||||
allowed_domains: Some(vec!["api.openai.com".to_string()]),
|
||||
denied_domains: Some(vec!["tracker.com".to_string(), "evil.com".to_string()]),
|
||||
allow_unix_sockets: Some(vec!["/tmp/a.sock".to_string()]),
|
||||
allow_local_binding: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
user_config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
|
||||
let mut expected_config = NetworkProxyConfig::default();
|
||||
expected_config.network.enabled = false;
|
||||
expected_config.network.proxy_url = "http://127.0.0.1:43128".to_string();
|
||||
expected_config.network.mode = NetworkMode::Limited;
|
||||
expected_config.network.allow_upstream_proxy = false;
|
||||
expected_config.network.dangerously_allow_non_loopback_proxy = false;
|
||||
expected_config.network.enable_socks5 = true;
|
||||
expected_config.network.socks_url = "http://127.0.0.1:43129".to_string();
|
||||
expected_config.network.enable_socks5_udp = true;
|
||||
expected_config.network.allowed_domains = vec!["api.openai.com".to_string()];
|
||||
expected_config.network.denied_domains =
|
||||
vec!["tracker.com".to_string(), "evil.com".to_string()];
|
||||
expected_config.network.allow_unix_sockets = vec!["/tmp/a.sock".to_string()];
|
||||
expected_config.network.allow_local_binding = false;
|
||||
|
||||
assert_eq!(spec.config, expected_config);
|
||||
assert_eq!(
|
||||
spec.constraints,
|
||||
NetworkProxyConstraints {
|
||||
enabled: Some(true),
|
||||
mode: None,
|
||||
allow_upstream_proxy: Some(true),
|
||||
dangerously_allow_non_loopback_proxy: Some(true),
|
||||
dangerously_allow_all_unix_sockets: None,
|
||||
allowed_domains: Some(vec!["*.openai.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(true),
|
||||
denied_domains: Some(vec!["evil.com".to_string()]),
|
||||
denylist_expansion_enabled: Some(true),
|
||||
allow_unix_sockets: Some(vec!["/tmp/a.sock".to_string(), "/tmp/b.sock".to_string()]),
|
||||
allow_local_binding: Some(true),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_requirements_clamp_and_union_user_network_settings() -> std::io::Result<()> {
|
||||
let mut user_config = NetworkProxyConfig::default();
|
||||
user_config.network.enabled = true;
|
||||
user_config.network.mode = NetworkMode::Full;
|
||||
user_config.network.allow_upstream_proxy = true;
|
||||
user_config.network.dangerously_allow_non_loopback_proxy = true;
|
||||
user_config.network.denied_domains = vec!["tracker.com".to_string()];
|
||||
user_config.network.allow_local_binding = true;
|
||||
|
||||
let requirements = NetworkConstraints {
|
||||
enabled: Some(false),
|
||||
http_port: None,
|
||||
socks_port: None,
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: Some(false),
|
||||
dangerously_allow_all_unix_sockets: None,
|
||||
allowed_domains: None,
|
||||
managed_allowed_domains_only: None,
|
||||
denied_domains: Some(vec!["evil.com".to_string()]),
|
||||
allow_unix_sockets: None,
|
||||
allow_local_binding: Some(false),
|
||||
};
|
||||
|
||||
let user_network = crate::config::permissions::NetworkToml {
|
||||
enabled: Some(true),
|
||||
allow_upstream_proxy: Some(true),
|
||||
dangerously_allow_non_loopback_proxy: Some(true),
|
||||
mode: Some(NetworkMode::Full),
|
||||
denied_domains: Some(vec!["tracker.com".to_string()]),
|
||||
allow_local_binding: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
user_config,
|
||||
Some(&user_network),
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
|
||||
let mut expected_config = NetworkProxyConfig::default();
|
||||
expected_config.network.enabled = false;
|
||||
expected_config.network.mode = NetworkMode::Full;
|
||||
expected_config.network.allow_upstream_proxy = false;
|
||||
expected_config.network.dangerously_allow_non_loopback_proxy = false;
|
||||
expected_config.network.denied_domains =
|
||||
vec!["tracker.com".to_string(), "evil.com".to_string()];
|
||||
expected_config.network.allow_local_binding = false;
|
||||
|
||||
assert_eq!(spec.config, expected_config);
|
||||
assert_eq!(
|
||||
spec.constraints,
|
||||
NetworkProxyConstraints {
|
||||
enabled: Some(false),
|
||||
mode: None,
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: Some(false),
|
||||
dangerously_allow_all_unix_sockets: None,
|
||||
allowed_domains: None,
|
||||
allowlist_expansion_enabled: None,
|
||||
denied_domains: Some(vec!["evil.com".to_string()]),
|
||||
denylist_expansion_enabled: Some(true),
|
||||
allow_unix_sockets: None,
|
||||
allow_local_binding: Some(false),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_requirements_enable_proxy_when_user_omits_network_enabled() -> std::io::Result<()> {
|
||||
let user_config = NetworkProxyConfig::default();
|
||||
let requirements = NetworkConstraints {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
user_config,
|
||||
None,
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
|
||||
let mut expected_config = NetworkProxyConfig::default();
|
||||
expected_config.network.enabled = true;
|
||||
|
||||
assert_eq!(spec.config, expected_config);
|
||||
assert_eq!(
|
||||
spec.constraints,
|
||||
NetworkProxyConstraints {
|
||||
enabled: Some(true),
|
||||
..NetworkProxyConstraints::default()
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_requirements_seed_allowlist_when_user_omits_it() -> std::io::Result<()> {
|
||||
let user_config = NetworkProxyConfig::default();
|
||||
let requirements = NetworkConstraints {
|
||||
enabled: Some(true),
|
||||
allowed_domains: Some(vec!["*.openai.com".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spec = NetworkProxySpec::from_config_and_constraints(
|
||||
user_config,
|
||||
None,
|
||||
Some(requirements),
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
|
||||
let mut expected_config = NetworkProxyConfig::default();
|
||||
expected_config.network.enabled = true;
|
||||
expected_config.network.allowed_domains = vec!["*.openai.com".to_string()];
|
||||
|
||||
assert_eq!(spec.config, expected_config);
|
||||
assert_eq!(
|
||||
spec.constraints,
|
||||
NetworkProxyConstraints {
|
||||
enabled: Some(true),
|
||||
allowed_domains: Some(vec!["*.openai.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(true),
|
||||
..NetworkProxyConstraints::default()
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -69,8 +69,17 @@ pub struct NetworkToml {
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
#[schemars(with = "Option<NetworkModeSchema>")]
|
||||
pub mode: Option<NetworkMode>,
|
||||
/// When `requirements.toml` sets `experimental_network.allowed_domains`,
|
||||
/// values here may further narrow access but must stay within the managed
|
||||
/// allowlist (subset semantics).
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
/// When `requirements.toml` sets `experimental_network.denied_domains`,
|
||||
/// managed entries are always enforced. Values here can add more denied
|
||||
/// domains but cannot remove the managed ones (floor semantics).
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
/// When `requirements.toml` sets `experimental_network.allow_unix_sockets`,
|
||||
/// values here may further narrow access but must stay within the managed
|
||||
/// socket allowlist (subset semantics).
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -662,6 +662,7 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
.map(|network| network.value.clone());
|
||||
guardian_config.permissions.network = Some(NetworkProxySpec::from_config_and_constraints(
|
||||
live_network_config,
|
||||
/*user_network*/ None,
|
||||
network_constraints,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)?);
|
||||
|
||||
@@ -969,6 +969,7 @@ fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
let mut parent_config = test_config();
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig::default(),
|
||||
None,
|
||||
Some(NetworkConstraints {
|
||||
enabled: Some(true),
|
||||
allowed_domains: Some(vec!["github.com".to_string()]),
|
||||
@@ -1032,6 +1033,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
parent_network,
|
||||
None,
|
||||
None,
|
||||
parent_config.permissions.sandbox_policy.get(),
|
||||
)
|
||||
.expect("parent network proxy spec"),
|
||||
@@ -1055,6 +1057,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
live_network,
|
||||
None,
|
||||
None,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.expect("live network proxy spec")
|
||||
|
||||
Reference in New Issue
Block a user