feat(network-proxy): add websocket proxy env support (#11784)
## Summary - add managed proxy env wiring for websocket-specific variables (`WS_PROXY`/`WSS_PROXY`, including lowercase) - keep websocket proxy vars aligned with the existing managed HTTP proxy endpoint - add CONNECT regression tests to cover allowlist and denylist decisions (websocket tunnel path) - document websocket proxy usage and CONNECT policy behavior in the network proxy README ## Testing - just fmt - cargo test -p codex-network-proxy - cargo clippy -p codex-network-proxy Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
This commit is contained in:
parent
ad53574d58
commit
f2ad519a87
3 changed files with 80 additions and 0 deletions
|
|
@ -62,6 +62,8 @@ For HTTP(S) traffic:
|
|||
```bash
|
||||
export HTTP_PROXY="http://127.0.0.1:3128"
|
||||
export HTTPS_PROXY="http://127.0.0.1:3128"
|
||||
export WS_PROXY="http://127.0.0.1:3128"
|
||||
export WSS_PROXY="http://127.0.0.1:3128"
|
||||
```
|
||||
|
||||
For SOCKS5 traffic (when `enable_socks5 = true`):
|
||||
|
|
@ -83,6 +85,9 @@ When a request is blocked, the proxy responds with `403` and includes:
|
|||
In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. HTTPS `CONNECT` and SOCKS5 are
|
||||
blocked because they would bypass method enforcement.
|
||||
|
||||
Websocket clients typically tunnel `wss://` through HTTPS `CONNECT`; those CONNECT targets still go
|
||||
through the same host allowlist/denylist checks.
|
||||
|
||||
## Library API
|
||||
|
||||
`codex-network-proxy` can be embedded as a library with a thin API:
|
||||
|
|
|
|||
|
|
@ -828,6 +828,51 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_allows_allowlisted_host_in_full_mode() {
|
||||
let policy = NetworkProxySettings {
|
||||
allowed_domains: vec!["example.com".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
|
||||
let mut req = Request::builder()
|
||||
.method(Method::CONNECT)
|
||||
.uri("https://example.com:443")
|
||||
.header("host", "example.com:443")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let (response, _request) = http_connect_accept(None, req).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_denies_denylisted_host() {
|
||||
let policy = NetworkProxySettings {
|
||||
allowed_domains: vec!["**.openai.com".to_string()],
|
||||
denied_domains: vec!["api.openai.com".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
|
||||
let mut req = Request::builder()
|
||||
.method(Method::CONNECT)
|
||||
.uri("https://api.openai.com:443")
|
||||
.header("host", "api.openai.com:443")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_connect_accept(None, req).await.unwrap_err();
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
response.headers().get("x-proxy-error").unwrap(),
|
||||
"blocked-by-denylist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_network_attempt_id_reads_proxy_authorization_header() {
|
||||
let encoded = STANDARD.encode("codex-net-attempt-attempt-1:");
|
||||
|
|
|
|||
|
|
@ -272,6 +272,8 @@ impl Eq for NetworkProxy {}
|
|||
pub const PROXY_URL_ENV_KEYS: &[&str] = &[
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"WS_PROXY",
|
||||
"WSS_PROXY",
|
||||
"ALL_PROXY",
|
||||
"FTP_PROXY",
|
||||
"YARN_HTTP_PROXY",
|
||||
|
|
@ -290,6 +292,7 @@ pub const ALL_PROXY_ENV_KEYS: &[&str] = &["ALL_PROXY", "all_proxy"];
|
|||
pub const ALLOW_LOCAL_BINDING_ENV_KEY: &str = "CODEX_NETWORK_ALLOW_LOCAL_BINDING";
|
||||
|
||||
const FTP_PROXY_ENV_KEYS: &[&str] = &["FTP_PROXY", "ftp_proxy"];
|
||||
const WEBSOCKET_PROXY_ENV_KEYS: &[&str] = &["WS_PROXY", "WSS_PROXY", "ws_proxy", "wss_proxy"];
|
||||
|
||||
pub const NO_PROXY_ENV_KEYS: &[&str] = &[
|
||||
"NO_PROXY",
|
||||
|
|
@ -375,6 +378,9 @@ fn apply_proxy_env_overrides(
|
|||
],
|
||||
&http_proxy_url,
|
||||
);
|
||||
// Some websocket clients look for dedicated WS/WSS proxy environment variables instead of
|
||||
// HTTP(S)_PROXY. Keep them aligned with the managed HTTP proxy endpoint.
|
||||
set_env_keys(env, WEBSOCKET_PROXY_ENV_KEYS, &http_proxy_url);
|
||||
|
||||
// Keep local/private targets direct so local IPC and metadata endpoints avoid the proxy.
|
||||
set_env_keys(env, NO_PROXY_ENV_KEYS, DEFAULT_NO_PROXY_VALUE);
|
||||
|
|
@ -728,6 +734,14 @@ mod tests {
|
|||
assert_eq!(has_proxy_url_env_vars(&env), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_proxy_url_env_vars_detects_websocket_proxy_keys() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("wss_proxy".to_string(), "http://127.0.0.1:3128".to_string());
|
||||
|
||||
assert_eq!(has_proxy_url_env_vars(&env), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_sets_common_tool_vars() {
|
||||
let mut env = HashMap::new();
|
||||
|
|
@ -744,6 +758,14 @@ mod tests {
|
|||
env.get("HTTP_PROXY"),
|
||||
Some(&"http://127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("WS_PROXY"),
|
||||
Some(&"http://127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("WSS_PROXY"),
|
||||
Some(&"http://127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("npm_config_proxy"),
|
||||
Some(&"http://127.0.0.1:3128".to_string())
|
||||
|
|
@ -810,6 +832,14 @@ mod tests {
|
|||
env.get("HTTPS_PROXY"),
|
||||
Some(&"http://codex-net-attempt-attempt-123@127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("WS_PROXY"),
|
||||
Some(&"http://codex-net-attempt-attempt-123@127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("WSS_PROXY"),
|
||||
Some(&"http://codex-net-attempt-attempt-123@127.0.0.1:3128".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("ALL_PROXY"),
|
||||
Some(&"http://codex-net-attempt-attempt-123@127.0.0.1:3128".to_string())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue