Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions codex-rs/app-server/tests/suite/v2/turn_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,96 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn()
Ok(())
}

#[tokio::test]
async fn turn_start_accepts_managed_network_profile_from_requirements() -> Result<()> {
let responses = vec![create_final_assistant_message_sse_response("Done")?];
let server = create_mock_responses_server_sequence_unchecked(responses).await;

let codex_home = TempDir::new()?;
create_config_toml(
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::from([(Feature::NetworkProxy, true)]),
)?;
std::fs::write(
codex_home.path().join("requirements.toml"),
r#"
default_permissions = "managed-network"

[allowed_permission_profiles]
managed-network = true
":read-only" = true

[permissions.managed-network]
extends = ":read-only"

[permissions.managed-network.network]
enabled = true
allow_local_binding = false

[permissions.managed-network.network.domains]
"packages.example" = "allow"
"#,
)?;

let mut app_server = TestAppServer::builder()
.with_codex_home(codex_home.path())
.build()
.await?;
timeout(DEFAULT_READ_TIMEOUT, app_server.initialize()).await??;

let thread_req = app_server
.send_thread_start_request_with_auto_env(ThreadStartParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await?;
let thread_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
app_server.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse {
thread,
active_permission_profile,
..
} = to_response::<ThreadStartResponse>(thread_resp)?;
let active_permission_profile =
active_permission_profile.context("expected active permission profile")?;
assert_eq!(active_permission_profile.id, "managed-network");

let turn_req = app_server
.send_turn_start_request(TurnStartParams {
thread_id: thread.id,
client_user_message_id: None,
input: vec![V2UserInput::Text {
text: "Use the managed network profile".to_string(),
text_elements: Vec::new(),
}],
permissions: Some("managed-network".to_string()),
..Default::default()
})
.await?;
let turn_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
app_server.read_stream_until_response_message(RequestId::Integer(turn_req)),
)
.await??;
let TurnStartResponse { turn } = to_response::<TurnStartResponse>(turn_resp)?;
assert!(
!turn.id.is_empty(),
"turn/start should resolve the managed profile's network configuration"
);
timeout(
DEFAULT_READ_TIMEOUT,
app_server.read_stream_until_notification_message("turn/completed"),
)
.await??;

Ok(())
}

#[tokio::test]
async fn turn_start_rejects_unknown_environment_before_starting_turn() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
Expand Down
29 changes: 22 additions & 7 deletions codex-rs/core/src/config/config_loader_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,9 @@ async fn system_requirements_define_managed_permission_profiles() -> anyhow::Res
codex_home.join(CONFIG_TOML_FILE),
r#"
default_permissions = "managed-standard"

[features]
network_proxy = true
"#,
)
.await?;
Expand All @@ -1378,6 +1381,11 @@ managed-standard = true

[permissions.managed-standard]
extends = ":workspace"

[permissions.managed-standard.network]
enabled = true
proxy_url = "http://127.0.0.1:43128"
enable_socks5 = false
"#,
)
.await?;
Expand All @@ -1399,13 +1407,20 @@ extends = ":workspace"
.allowed_permission_profiles,
Some(BTreeMap::from([("managed-standard".to_string(), true)]))
);
assert_eq!(
config
.permissions
.active_permission_profile()
.map(|profile| profile.id),
Some("managed-standard".to_string())
);
let active_permission_profile = config
.permissions
.active_permission_profile()
.expect("managed profile should be active");
assert_eq!(active_permission_profile.id, "managed-standard");

let network = config
.network_proxy_spec_for_active_permission_profile(
&active_permission_profile,
config.permissions.permission_profile(),
)?
.expect("managed profile should retain its network proxy configuration");
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
assert!(!network.socks_enabled());
Ok(())
}

Expand Down
6 changes: 5 additions & 1 deletion codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4204,8 +4204,12 @@ impl Config {
),
)
})?;
let mut configured_network_proxy_config = network_proxy_config_for_profile_selection(
let permissions = merge_managed_permission_profiles(
cfg.permissions.as_ref(),
self.config_layer_stack.requirements_toml(),
)?;
let mut configured_network_proxy_config = network_proxy_config_for_profile_selection(
permissions.as_ref(),
active_permission_profile.id.as_str(),
)?;
if self.features.enabled(Feature::NetworkProxy)
Expand Down
Loading