Motivation
Some oxhttp::Client users fetch URLs selected by a lower-trust caller and need to enforce an application-specific outbound destination policy (for example, SSRF protection for SPARQL SERVICE and LOAD).
The current client resolves inside the private get_and_validate_socket_addresses function immediately before connecting. A caller therefore cannot:
- inspect every DNS answer and reject policy-forbidden addresses;
- pin the exact validated addresses used by the connection, avoiding a validate-then-resolve-again DNS rebinding window;
- apply the same policy automatically to each redirect destination;
- preserve the original hostname for the HTTP
Host header and TLS SNI/certificate verification while connecting to a validated address.
Resolving and validating before calling Client::request is not sufficient because the client performs another resolution internally.
I do not think oxhttp should define a universal SSRF policy. The requested capability is a narrow extension point that lets applications supply their own resolution and address policy.
Possible API
One option would be a resolver trait or callback configured on Client, roughly:
pub trait Resolver: Send + Sync {
fn resolve(&self, uri: &http::Uri, default_port: u16)
-> std::io::Result<Vec<std::net::SocketAddr>>;
}
Client::new().with_resolver(...)
The default resolver could retain the current ToSocketAddrs behavior. single_request would use the configured resolver for the initial request and every followed redirect, then connect only to the returned addresses. The request URI hostname should remain unchanged for Host and TLS verification.
An address-filter callback after internal resolution would also help, but a resolver returning the approved addresses makes connection pinning explicit and avoids a second lookup.
Expected behavior
- The hook runs for every outbound connection, including redirect targets.
- All returned addresses are the exact candidates passed to
TcpStream::connect / connect_timeout.
- Resolver or policy errors are returned from
Client::request.
- Existing callers keep the current behavior without configuration.
- Existing bad-port validation either remains in the client or is clearly documented as part of the resolver contract.
Context
This came up while addressing CWE-918 findings in an oxhttp-based SPARQL server. The application can own scheme allowlisting, private/link-local/loopback address rules, deployment allowlists, response limits, and timeouts, but it cannot close the DNS rebinding gap without a resolver/connection hook in the HTTP transport.
Motivation
Some
oxhttp::Clientusers fetch URLs selected by a lower-trust caller and need to enforce an application-specific outbound destination policy (for example, SSRF protection for SPARQLSERVICEandLOAD).The current client resolves inside the private
get_and_validate_socket_addressesfunction immediately before connecting. A caller therefore cannot:Hostheader and TLS SNI/certificate verification while connecting to a validated address.Resolving and validating before calling
Client::requestis not sufficient because the client performs another resolution internally.I do not think oxhttp should define a universal SSRF policy. The requested capability is a narrow extension point that lets applications supply their own resolution and address policy.
Possible API
One option would be a resolver trait or callback configured on
Client, roughly:The default resolver could retain the current
ToSocketAddrsbehavior.single_requestwould use the configured resolver for the initial request and every followed redirect, then connect only to the returned addresses. The request URI hostname should remain unchanged forHostand TLS verification.An address-filter callback after internal resolution would also help, but a resolver returning the approved addresses makes connection pinning explicit and avoids a second lookup.
Expected behavior
TcpStream::connect/connect_timeout.Client::request.Context
This came up while addressing CWE-918 findings in an oxhttp-based SPARQL server. The application can own scheme allowlisting, private/link-local/loopback address rules, deployment allowlists, response limits, and timeouts, but it cannot close the DNS rebinding gap without a resolver/connection hook in the HTTP transport.