mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
fix(network-proxy): recheck network proxy connect targets (#19999)
## Why The proxy checks the requested host before opening the upstream connection, but DNS can resolve an allowed hostname to a loopback, private, or other non-public address after that first decision. Without a final check on the actual socket target, a request that looks acceptable at the hostname layer can still connect to a local service once resolution completes. ## What changed - add a shared TCP connector check for direct proxy egress - use that path for HTTP, `CONNECT`, SOCKS5, and MITM upstream connections - keep configured upstream proxy hops on the existing proxy path - add direct-connector coverage for allowed and rejected local targets ## Security impact Direct proxy egress now rechecks the resolved socket address before connecting, closing the gap between hostname policy evaluation and the final network target. ## Verification - `cargo test -p codex-network-proxy` --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::config::NetworkMode;
|
||||
use crate::connect_policy::TargetCheckedTcpConnector;
|
||||
use crate::mitm;
|
||||
use crate::network_policy::BlockDecisionAuditEventArgs;
|
||||
use crate::network_policy::NetworkDecision;
|
||||
@@ -66,7 +67,6 @@ use rama_net::proxy::ProxyTarget;
|
||||
use rama_net::proxy::StreamForwardService;
|
||||
use rama_net::stream::SocketInfo;
|
||||
use rama_tcp::client::Request as TcpRequest;
|
||||
use rama_tcp::client::service::TcpConnector;
|
||||
use rama_tcp::server::TcpListener;
|
||||
use rama_tls_rustls::client::TlsConnectorDataBuilder;
|
||||
use rama_tls_rustls::client::TlsConnectorLayer;
|
||||
@@ -345,20 +345,22 @@ async fn http_connect_proxy(upgraded: Upgraded) -> Result<(), Infallible> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let allow_upstream_proxy = match upgraded
|
||||
let app_state = match upgraded
|
||||
.extensions()
|
||||
.get::<Arc<NetworkProxyState>>()
|
||||
.cloned()
|
||||
{
|
||||
Some(state) => match state.allow_upstream_proxy().await {
|
||||
Ok(allowed) => allowed,
|
||||
Err(err) => {
|
||||
error!("failed to read upstream proxy setting: {err}");
|
||||
false
|
||||
}
|
||||
},
|
||||
Some(state) => state,
|
||||
None => {
|
||||
error!("missing app state");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let allow_upstream_proxy = match app_state.allow_upstream_proxy().await {
|
||||
Ok(allowed) => allowed,
|
||||
Err(err) => {
|
||||
error!("failed to read upstream proxy setting: {err}");
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -369,7 +371,7 @@ async fn http_connect_proxy(upgraded: Upgraded) -> Result<(), Infallible> {
|
||||
None
|
||||
};
|
||||
|
||||
if let Err(err) = forward_connect_tunnel(upgraded, proxy).await {
|
||||
if let Err(err) = forward_connect_tunnel(upgraded, proxy, app_state).await {
|
||||
warn!("tunnel error: {err}");
|
||||
}
|
||||
Ok(())
|
||||
@@ -378,6 +380,7 @@ async fn http_connect_proxy(upgraded: Upgraded) -> Result<(), Infallible> {
|
||||
async fn forward_connect_tunnel(
|
||||
upgraded: Upgraded,
|
||||
proxy: Option<ProxyAddress>,
|
||||
app_state: Arc<NetworkProxyState>,
|
||||
) -> Result<(), BoxError> {
|
||||
let authority = upgraded
|
||||
.extensions()
|
||||
@@ -392,7 +395,7 @@ async fn forward_connect_tunnel(
|
||||
|
||||
let req = TcpRequest::new_with_extensions(authority.clone(), extensions)
|
||||
.with_protocol(Protocol::HTTPS);
|
||||
let proxy_connector = HttpProxyConnector::optional(TcpConnector::new());
|
||||
let proxy_connector = HttpProxyConnector::optional(TargetCheckedTcpConnector::new(app_state));
|
||||
let tls_config = TlsConnectorDataBuilder::new()
|
||||
.with_alpn_protocols_http_auto()
|
||||
.build();
|
||||
@@ -730,9 +733,9 @@ async fn http_plain_proxy(
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let client = if allow_upstream_proxy {
|
||||
UpstreamClient::from_env_proxy()
|
||||
UpstreamClient::from_env_proxy(app_state.clone())
|
||||
} else {
|
||||
UpstreamClient::direct()
|
||||
UpstreamClient::direct(app_state.clone())
|
||||
};
|
||||
|
||||
// Strip hop-by-hop headers only after extracting metadata used for policy correlation.
|
||||
|
||||
Reference in New Issue
Block a user