Files
codex/codex-rs/network-proxy/src/remote_config_tests.rs
T
viyatb-oai 3a08af44b2 Handle exec-server network policy requests in the client (#35359)
## What changed

- Add client-side handling for exec-server network policy requests, including request validation, per-process decision routing, and allow, deny, or ask responses.
- Bound concurrent callbacks and fail closed when requests are invalid, exceed capacity, time out, or outlive their process session.
- Replace the callback opt-in flag with a launch-level `policyDecisionTimeoutMs` value and include transport overhead in the executor timeout.
- Preserve callback admission limits across connection recovery and clean up policy state when process startup is abandoned or a session ends.

## Testing

- Cover decision forwarding, invalid and excess requests, cancellation during process cleanup, abandoned starts, timeout propagation, and launch-config serialization.

GitOrigin-RevId: 6ee2a0139e0f8170cf1a5cdd646bd811b7d3c1f6
2026-07-25 16:03:00 +00:00

139 lines
4.6 KiB
Rust

use pretty_assertions::assert_eq;
use super::RemoteNetworkProxyConfig;
use super::RemoteNetworkProxyLaunchConfig;
use crate::MitmHookConfig;
use crate::NetworkMode;
use crate::NetworkProxyAuditMetadata;
use crate::NetworkProxyConfig;
use crate::NetworkProxyState;
#[test]
fn round_trip_preserves_supported_effective_settings() {
let mut config = NetworkProxyConfig {
enabled: true,
enable_socks5: false,
enable_socks5_udp: false,
allow_upstream_proxy: false,
dangerously_allow_all_unix_sockets: true,
mode: NetworkMode::Limited,
allow_local_binding: true,
..NetworkProxyConfig::default()
};
config.set_allowed_domains(vec!["example.com".into()]);
config.set_denied_domains(vec!["blocked.example.com".into()]);
config.set_allow_unix_sockets(vec!["/var/run/example.sock".into()]);
let remote =
RemoteNetworkProxyConfig::from_effective_config(&config).expect("supported remote config");
let round_trip = remote.into_network_proxy_config();
assert_eq!(round_trip, config);
}
#[test]
fn rejects_unsupported_configuration() {
let cases = [
(
"MITM",
NetworkProxyConfig {
enabled: true,
mitm: true,
..NetworkProxyConfig::default()
},
),
(
"credential broker",
NetworkProxyConfig {
enabled: true,
credential_broker: true,
..NetworkProxyConfig::default()
},
),
(
"plaintext credential injection",
NetworkProxyConfig {
enabled: true,
dangerously_allow_plaintext_credential_injection: true,
..NetworkProxyConfig::default()
},
),
(
"MITM hooks",
NetworkProxyConfig {
enabled: true,
mitm_hooks: vec![MitmHookConfig::default()],
..NetworkProxyConfig::default()
},
),
];
for (feature, config) in cases {
assert!(
RemoteNetworkProxyConfig::from_effective_config(&config).is_err(),
"{feature} must not cross the remote executor boundary"
);
}
}
#[test]
fn accepts_unsupported_configuration_when_proxy_is_disabled() {
let config = NetworkProxyConfig {
mitm: true,
credential_broker: true,
dangerously_allow_plaintext_credential_injection: true,
mitm_hooks: vec![MitmHookConfig::default()],
..NetworkProxyConfig::default()
};
let remote = RemoteNetworkProxyConfig::from_effective_config(&config)
.expect("disabled proxy configuration does not cross the executor boundary");
assert!(!remote.enabled);
}
#[test]
fn launch_config_materializes_audit_and_execution_attribution() {
let proxy = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig {
enabled: true,
..NetworkProxyConfig::default()
})
.expect("supported remote config");
let audit_metadata = NetworkProxyAuditMetadata {
conversation_id: Some("conversation-1".to_string()),
user_account_id: Some("account-1".to_string()),
originator: Some("codex_cli_rs".to_string()),
model: Some("model-1".to_string()),
..NetworkProxyAuditMetadata::default()
};
let state = NetworkProxyState::from_remote_launch_config(RemoteNetworkProxyLaunchConfig {
proxy,
audit_metadata: audit_metadata.clone(),
environment_id: Some("remote".to_string()),
execution_id: Some("execution-1".to_string()),
policy_decision_timeout_ms: None,
})
.expect("remote launch state");
assert_eq!(state.audit_metadata(), &audit_metadata);
assert_eq!(state.environment_id(), Some("remote"));
assert_eq!(state.execution_id().as_deref(), Some("execution-1"));
}
#[test]
fn policy_decision_callback_timeout_round_trips() {
let config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig::default())
.expect("supported remote config");
let mut launch = RemoteNetworkProxyLaunchConfig::new(config);
let without_timeout = serde_json::to_value(&launch).expect("serialize launch config");
assert_eq!(without_timeout.get("policyDecisionTimeoutMs"), None);
launch.policy_decision_timeout_ms = Some(900_000);
let with_timeout = serde_json::to_value(&launch).expect("serialize launch timeout");
assert_eq!(with_timeout["policyDecisionTimeoutMs"], 900_000);
assert_eq!(
serde_json::from_value::<RemoteNetworkProxyLaunchConfig>(with_timeout)
.expect("deserialize launch timeout"),
launch
);
}