mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
## What changed Allow shared network constraints in `[permissions.network]` in `requirements.toml`, alongside `[permissions.filesystem]`. Apply them throughout configuration loading, network approvals, Windows sandbox policy validation, and `configRequirements/read`, and show their source in `/debug-config`. Retain `[network_proxy]` and `[experimental_network]`, but reject mixing either with `[permissions.network]`, even across requirements layers. Preserve independent network and filesystem precedence and source attribution, and report unknown nested network fields. Reserve `network` as a managed requirements table name and reject empty or profile-shaped tables with migration guidance. Profiles named `network` remain valid in `config.toml`. Document that policy publishers must keep using `[experimental_network]` until all readers support the chosen replacement. ## Testing Add coverage for layered constraints and source attribution, conflicting spellings, profile-name migration errors, unknown-field diagnostics, and cloud validation. Extend API, project discovery, network approval, and Windows policy tests to cover `[permissions.network]`, and verify managed proxy settings remain independent of disabled feature configuration. GitOrigin-RevId: 994dfca9abf5a7aea914276c0ded8b40d7ed0372
335 lines
10 KiB
Rust
335 lines
10 KiB
Rust
use anyhow::Result;
|
|
use codex_config::ConfigRequirements;
|
|
use codex_config::ConfigRequirementsWithSources;
|
|
use codex_config::RequirementSource;
|
|
use codex_windows_sandbox::WindowsSandboxProvisioningSettings;
|
|
use codex_windows_sandbox::WindowsSandboxProxyListeners;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use crate::ipc::ProvisioningRequest;
|
|
|
|
const NETWORK_TABLES: [&str; 3] = [
|
|
"network_proxy",
|
|
"experimental_network",
|
|
"permissions.network",
|
|
];
|
|
|
|
fn validate_requirements(
|
|
settings: &WindowsSandboxProvisioningSettings,
|
|
listeners: WindowsSandboxProxyListeners,
|
|
contents: &str,
|
|
) -> Result<()> {
|
|
let mut requirements_with_sources = ConfigRequirementsWithSources::default();
|
|
requirements_with_sources
|
|
.merge_unset_fields(RequirementSource::Unknown, toml::from_str(contents)?);
|
|
let requirements = ConfigRequirements::try_from(requirements_with_sources)?;
|
|
super::validate_requirements(settings, &listeners, &requirements)
|
|
}
|
|
|
|
#[test]
|
|
fn only_registered_refresh_skips_configuration_loading() {
|
|
for (registered_core, refresh_only) in
|
|
[(true, true), (true, false), (false, false), (false, true)]
|
|
{
|
|
let request = ProvisioningRequest {
|
|
codex_home: std::env::temp_dir(),
|
|
registered_core,
|
|
refresh_only,
|
|
settings: WindowsSandboxProvisioningSettings::default(),
|
|
listeners: WindowsSandboxProxyListeners::default(),
|
|
};
|
|
// An invalid worker token makes any attempt to load config fail closed.
|
|
// Refresh must succeed without starting that runtime; setup must not.
|
|
let result = super::validate_provisioning_request(
|
|
&request.codex_home,
|
|
&request,
|
|
std::ptr::null_mut(),
|
|
);
|
|
assert_eq!(result.is_ok(), registered_core && refresh_only);
|
|
if let Err(error) = result {
|
|
assert!(error.to_string().contains("failed to impersonate"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unmanaged_policy_allows_default_and_requested_network_settings() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
"",
|
|
)?;
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![3128, 8080, 9000],
|
|
allow_local_binding: true,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![8080],
|
|
},
|
|
"allowed_sandbox_modes = [\"read-only\", \"workspace-write\"]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn elevated_sandbox_is_allowed_when_included() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
"[windows]\nallowed_sandbox_implementations = [\"unelevated\", \"elevated\"]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn elevated_sandbox_is_rejected_when_prohibited_or_empty() {
|
|
for implementations in ["[\"unelevated\"]", "[]"] {
|
|
let policy = format!("[windows]\nallowed_sandbox_implementations = {implementations}");
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
&policy,
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn forbidden_local_binding_is_rejected() {
|
|
let settings = WindowsSandboxProvisioningSettings {
|
|
proxy_ports: Vec::new(),
|
|
allow_local_binding: true,
|
|
};
|
|
for table in NETWORK_TABLES {
|
|
let policy = format!("[{table}]\nallow_local_binding = false");
|
|
let error =
|
|
validate_requirements(&settings, WindowsSandboxProxyListeners::default(), &policy)
|
|
.expect_err("local binding must be rejected");
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"managed policy does not permit local network binding",
|
|
"{table}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn disabled_network_rejects_proxy_ports_and_local_binding() -> Result<()> {
|
|
for table in NETWORK_TABLES {
|
|
let policy = format!("[{table}]\nenabled = false");
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
&policy,
|
|
)?;
|
|
for settings in [
|
|
WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![3128],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProvisioningSettings {
|
|
proxy_ports: Vec::new(),
|
|
allow_local_binding: true,
|
|
},
|
|
] {
|
|
assert!(
|
|
validate_requirements(&settings, WindowsSandboxProxyListeners::default(), &policy)
|
|
.is_err(),
|
|
"{table}"
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn enabled_network_allows_unrestricted_proxy_ports() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![3128, 8080, 8081, 9000],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128, 8080],
|
|
socks_ports: vec![8081],
|
|
},
|
|
"[experimental_network]\nenabled = true",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn disabled_local_binding_is_allowed_when_policy_permits_binding() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
"[experimental_network]\nallow_local_binding = true",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn managed_proxy_ports_allow_only_configured_ports() -> Result<()> {
|
|
let allowed = WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![1080, 3128],
|
|
allow_local_binding: false,
|
|
};
|
|
let prohibited = WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![1080, 3128, 8080],
|
|
allow_local_binding: false,
|
|
};
|
|
for table in NETWORK_TABLES {
|
|
let policy = format!("[{table}]\nhttp_port = 3128\nsocks_port = 1080");
|
|
for listeners in [
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![1080],
|
|
},
|
|
WindowsSandboxProxyListeners::default(),
|
|
] {
|
|
validate_requirements(&allowed, listeners, &policy)?;
|
|
}
|
|
for listeners in [
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128, 8080],
|
|
socks_ports: vec![1080],
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![1080, 8080],
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![1080],
|
|
},
|
|
WindowsSandboxProxyListeners::default(),
|
|
] {
|
|
assert!(
|
|
validate_requirements(&prohibited, listeners, &policy).is_err(),
|
|
"{table}"
|
|
);
|
|
}
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
&policy,
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn managed_http_port_does_not_restrict_the_unmanaged_socks_listener() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![3128, 8081],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![8081],
|
|
},
|
|
"[experimental_network]\nhttp_port = 3128",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn managed_socks_port_does_not_restrict_the_unmanaged_http_listener() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![1080, 3128],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![1080],
|
|
},
|
|
"[experimental_network]\nsocks_port = 1080",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn managed_proxy_ports_reject_swapped_listener_roles() {
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![1080, 3128],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![1080],
|
|
socks_ports: vec![3128],
|
|
},
|
|
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn managed_socks_port_rejects_a_mismatched_socks_listener() {
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![1080, 3128, 8081],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: vec![1080, 8081],
|
|
},
|
|
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn managed_socks_port_does_not_require_a_disabled_socks_listener() -> Result<()> {
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings {
|
|
proxy_ports: vec![3128],
|
|
allow_local_binding: false,
|
|
},
|
|
WindowsSandboxProxyListeners {
|
|
http_ports: vec![3128],
|
|
socks_ports: Vec::new(),
|
|
},
|
|
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_machine_policy_fails_closed() {
|
|
for table in NETWORK_TABLES {
|
|
let policy = format!("[{table}]\nhttp_port = \"invalid\"");
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
&policy,
|
|
)
|
|
.is_err(),
|
|
"{table}"
|
|
);
|
|
}
|
|
for table in ["network_proxy", "experimental_network"] {
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
&format!("[{table}]\nenabled = true\n[permissions.network]\nenabled = true")
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
assert!(
|
|
validate_requirements(
|
|
&WindowsSandboxProvisioningSettings::default(),
|
|
WindowsSandboxProxyListeners::default(),
|
|
"[windows]\nallowed_sandbox_implementations = [\"invalid\"]"
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|