A URL can pass validation while the request it eventually produces reaches a forbidden destination. For a server-side importer, image fetcher or link preview, the useful review question is not only “Was this hostname allowed?” It is “Which destination was authorized, and did every connection use that decision?”

There are two distinct changes to watch: the same hostname can resolve differently later, and an HTTP redirect can introduce another URL. A boolean saved with a URL does not describe either subsequent connection. This guide follows those boundaries for server-side HTTP fetching; it is not a complete SSRF library or a tested exploit.

What exactly did your validator approve?

First define the feature’s destination policy. A fixed integration can name approved hosts and ports. A public-web importer needs a broader policy and stronger isolation. These are different products, not two configurations with identical risk. OWASP explicitly separates known-destination applications from applications that accept arbitrary external destinations, and recommends both application and network controls. OWASP SSRF prevention.

Parse the URL and examine its components rather than checking whether the string starts with an approved name. GitLab’s development guidance demonstrates why prefix and suffix comparisons can identify the wrong host. Define the schemes, ports and destination identities your feature actually needs. A syntactically valid URL is not an authorization decision. GitLab’s SSRF guidance.

For a review, write down three separate outputs:

  • Parsed identity: the scheme, hostname and port the client will use.
  • Destination decision: the addresses permitted under this feature’s policy.
  • Connection instruction: how the transport is constrained to those addresses.

If the third output is just the original URL, find out whether the HTTP client will perform a new, unchecked lookup.

Why can a second DNS lookup undo the check?

A check at configuration-save time says little about a connection made by tomorrow’s background job. Even immediately adjacent validation and fetching can be separate operations. GitLab documents this time-of-check/time-of-use problem and a case where ignoring the URL blocker’s returned value discarded its protection. Its Ruby guidance says the DNS-rebinding option returns a URI containing the resolved IP; merely calling the validator is insufficient. This is an integration lesson, not a claim about your library’s API. GitLab TOCTOU guidance, Ruby URL blocker.

OWASP’s connection-time recommendation is to validate resolved addresses, connect only to those addresses and apply the policy to retries and fallback connections too. Domain monitoring is additional detection, not a substitute. Include IPv4 and IPv6 in the design. OWASP domain-name controls.

Four boundaries: parse the URL, authorize resolved destinations, constrain the connection while preserving TLS hostname identity, and either stop redirects or validate the next hop from the beginning.

An original review map, not an implemented networking stack. Each new redirect target goes through the full decision again; retries cannot silently introduce an unchecked address.

How do you keep TLS verification when connecting to an IP?

Do not solve the lookup problem by disabling certificate verification. The intended hostname still matters for HTTP routing, TLS SNI and certificate-name checks. The everything curl documentation distinguishes changing a Host header from supplying a custom resolution: a Host header alone does not set HTTPS SNI, whereas a custom resolution preserves the URL’s hostname for TLS. curl name-resolution documentation.

In libcurl, CURLOPT_RESOLVE supplies addresses for a hostname-and-port pair through its DNS cache. That is a transport capability, not an SSRF policy: it does not decide which addresses your application should authorize. It also has cache-lifetime semantics you must account for. libcurl’s option reference.

Connection pools need their own review. curl checks for an existing reusable connection before DNS resolution, so changing a resolution override alone does not prove that a reused socket satisfies the current policy. curl connection reuse.

For another client, ask for the equivalent documented mechanism. If an outbound proxy resolves the destination instead, the review must reach that proxy: a local lookup does not establish which remote destination it opens. Treat an unexamined resolver or proxy path as an unresolved boundary, not proof of protection.

Does an allowed first hop make a redirect safe?

No. MDN recommends avoiding automatic redirects, validating redirected URLs when following them, and bounding redirect chains. Disabling redirects is a reasonable product choice when they are unnecessary; supporting them means authorizing another request, not approving a Location string once. MDN redirect defenses.

Credentials are a separate decision. libcurl documents default protections for Authorization and explicitly supplied Cookie headers on cross-host redirects, but warns that other headers can still carry sensitive data. Do not assume a custom API-key header receives identical treatment. Check the method, body and headers that each permitted hop can receive. libcurl redirect behavior.

Here is a fictional design review, not network traffic we generated. The rows are independent branches to review, not a chronological packet trace. “Address A” means a destination allowed by the example policy; “address B” means one it forbids. No address classification is implied by the labels.

StageWhat happensRequired decision
IntakeAn importer accepts a permitted HTTPS hostname.Approve the parsed destination identity, not just its text.
ResolutionThe hostname returns address A.Validate A and constrain the connection to approved results.
RetryAnother lookup would return address B.Do not use B merely because the earlier attempt passed.
TLSThe chosen IP presents a certificate for a different name.Reject the identity mismatch; do not disable verification.
ResponseA permitted server supplies a redirect.Stop, or authorize the next URL and its destination before connecting.

The trace exposes three different failure classes: changed resolution, changed target and wrong TLS identity. One successful first request does not exercise the other branches.

What evidence should a release review require?

Use this acceptance worksheet with your exact client version and deployment route. These are proposed checks for an authorized isolated environment, not checks performed for this article:

  • Can you show which parsed host, resolved address and connection destination were used, without recording signed URLs, credentials or response secrets?
  • Does every connection stay on an authorized address or fail closed, including retries, connection-pool reuse and alternate-address paths?
  • Does a disallowed redirect stop before a connection is opened, and does a permitted redirect repeat all relevant checks?
  • Does preserving the hostname still reject a wrong certificate?
  • Can the fetcher reach internal services it does not need, even if application validation fails?

MDN recommends least privilege and isolation for request-making services. The last question therefore belongs to the deployment review, not only the URL utility’s unit tests. MDN isolation guidance.

An allowed network destination also does not make downloaded content trustworthy or authorize every action on that server. Keep destination control, application authorization and content handling separate. Browser CORS failures concern a different boundary; they are not evidence that a server-side fetch was blocked. And when a fetch uses a presigned URL, destination approval does not make that bearer URL safe to retain in logs.

Sources & scope

Primary documentation review and an original fictional request trace. No HTTP client, DNS rebinding attack or live service was tested.

  1. OWASP SSRF Prevention Cheat Sheet
  2. GitLab secure coding guidelines
  3. GitLab Ruby URL blocker guidance
  4. MDN SSRF defenses
  5. everything curl — name resolution
  6. libcurl CURLOPT_RESOLVE
  7. everything curl — connection reuse
  8. libcurl CURLOPT_FOLLOWLOCATION
Publishing details & corrections

Prepared with AI assistance. Sources, any tests performed, and their limitations are described in the article.

No material corrections recorded for this version.

Send a correction

Back to writingEditorial standards