mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +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
128 lines
4.2 KiB
Rust
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(())
|
|
}
|