Post

SSRF Defense Belongs at the Connection Boundary

Secure URL-fetching services with destination policy, redirect controls, isolated execution, and tests for DNS changes.

SSRF Defense Belongs at the Connection Boundary

A URL preview feature gives the application a small but powerful capability: make a network request on behalf of an untrusted user. Validating that a string begins with https:// does not determine where the server will connect.

I would begin the design review by asking whether arbitrary destinations are necessary. An integration with three known providers can use a narrow destination policy. A general-purpose preview service needs a more constrained execution environment because its destination set is inherently broad.

Separate parsing from connection authorization

Use one well-understood URL parser and reject unsupported schemes, embedded credentials, and ports outside the service contract. Resolve the hostname and evaluate every candidate address against the destination policy, including IPv6 and mapped-address forms.

A subtle failure appears when validation resolves the hostname once but the HTTP client resolves it again before connecting. The answers can differ. The connection mechanism must enforce the policy on the actual destination, while preserving the intended hostname for TLS verification. A naive substitution of the hostname with an IP address can break certificate validation or virtual hosting.

Redirects are another new request. Disable automatic following by default, or repeat the full policy and connection checks for every hop. OWASP discusses allowlisting, redirect handling, and DNS pitfalls in its SSRF prevention guidance.

Give the fetcher fewer things to reach

My proposed architecture is a dedicated worker behind an egress policy, with no production credentials and no route to internal administrative services. It returns a bounded result to the application rather than streaming arbitrary remote content directly into a privileged parser.

The worker needs separate limits for connection establishment, total elapsed time, response bytes, and decompressed bytes. A small compressed response can expand dramatically. Limit concurrent requests per tenant as well as globally so one customer cannot monopolize the worker pool.

A useful review artifact is this test matrix:

Input or conditionRequired result
Approved public host redirects to an internal addressRedirect rejected
DNS answer changes between validation and connectionActual destination still checked
Remote server never finishes the bodyDeadline terminates the fetch
Compressed body exceeds the expanded-size budgetProcessing stops before resource exhaustion
Worker attempts an internal administrative endpointNetwork boundary blocks it

Make ownership match the failure surface

The application team owns URL semantics and response handling. The platform team owns the network boundary and destination enforcement. Security owns adversarial cases and confirms the worker has no ambient credentials. None of those owners can prove the whole control alone.

I would launch a narrow destination set first, inspect denied-request categories, and expand only when a product use case justifies the additional reachability. Exceptions should name the destination, owner, purpose, and expiry. “Allow all because one preview failed” is an architectural change and deserves an architectural decision.

This post is licensed under CC BY 4.0 by the author.