Skip to content

Allow Client callers to customize DNS resolution / validated socket addresses #85

Description

@eljeffeg

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions