Files
codex/codex-rs/app-server/tests/suite/v2/config_requirements_application.rs
T
viyatb-oai dfcb5b5994 Support managed network requirements under permissions.network (#58182)
## 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
2026-10-29 22:36:06 +00:00

128 lines
4.2 KiB
Rust

//! Public requirements responses expose managed application policy.
use anyhow::Result;
use app_test_support::TestAppServer;
use codex_app_server_protocol::RequestId;
use pretty_assertions::assert_eq;
use serde_json::Value;
use serde_json::json;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;
const READ_TIMEOUT: Duration = Duration::from_secs(/*secs*/ 60);
async fn start_server(home: &TempDir) -> Result<TestAppServer> {
TestAppServer::builder()
.with_codex_home(home.path())
.build_initialized_with_timeout(READ_TIMEOUT)
.await
}
async fn read_requirements(server: &mut TestAppServer) -> Result<Value> {
let request_id = server.send_config_requirements_read_request().await?;
timeout(READ_TIMEOUT, server.read_response(request_id)).await?
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn application_network_defaults_and_null_base_url_are_exposed() -> Result<()> {
for (contents, application) in [
("", Value::Null),
("[application]", json!({"network": null, "storage": null})),
(
"[application.network]",
json!({"network": {"enabled": true, "domains": {}}, "storage": null}),
),
(
"[application.network]\nenabled = false",
json!({"network": {"enabled": false, "domains": {}}, "storage": null}),
),
] {
let home = TempDir::new()?;
std::fs::write(
home.path().join("requirements.toml"),
format!("allow_remote_control = false\n{contents}"),
)?;
let mut server = start_server(&home).await?;
let wire = read_requirements(&mut server).await?;
let requirements = wire["requirements"]
.as_object()
.expect("requirements object");
assert_eq!(
(
requirements.get("application"),
requirements.get("chatgptBaseUrl")
),
(Some(&application), Some(&Value::Null)),
);
}
let home = TempDir::new()?;
let mut server = start_server(&home).await?;
assert_eq!(
read_requirements(&mut server).await?,
json!({"requirements": null, "supportsIndependentSpeedModes": true})
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn application_destinations_stay_separate_from_agent_network() -> Result<()> {
for table in [
"network_proxy",
"experimental_network",
"permissions.network",
] {
let home = TempDir::new()?;
std::fs::write(
home.path().join("requirements.toml"),
format!(
r#"
chatgpt_base_url = "https://managed.example.com/backend-api/"
[{table}.domains]
"github.com" = "allow"
[application.network.domains]
"GOV.example.com." = "allow"
"github.com" = "deny"
"#
),
)?;
let mut server = start_server(&home).await?;
let wire = read_requirements(&mut server).await?;
assert_eq!(
(
&wire["requirements"]["application"],
&wire["requirements"]["network"]["domains"],
&wire["requirements"]["chatgptBaseUrl"]
),
(
&json!({"network": {"enabled": true, "domains": {"gov.example.com": "allow", "github.com": "deny"}}, "storage": null}),
&json!({"github.com": "allow"}),
&json!("https://managed.example.com/backend-api/")
),
"{table}"
);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn application_requirements_read_rejects_invalid_configuration() -> Result<()> {
let home = TempDir::new()?;
let mut server = start_server(&home).await?;
std::fs::write(
home.path().join("requirements.toml"),
"[application.network.domains]\n'*.example.com' = 'allow'",
)?;
let request_id = server.send_config_requirements_read_request().await?;
let error = timeout(
READ_TIMEOUT,
server.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert!(
error.error.message.contains("application.network.domains"),
"{error:?}"
);
Ok(())
}