Safeguard
Application Security•

How do you avoid SSRF vulnerabilities in Go applications?

Avoid SSRF in Go by allow listing destinations, checking the resolved IP at connect time, limiting redirects and blocking internal ranges. A defence plan.

Safeguard Security Team
5 min read

To avoid server side request forgery (SSRF) in Go, never fetch a user supplied URL freely. Allow list destinations where you can, validate the IP address at connection time rather than only the hostname, control redirects, and keep the service's network access narrow.

What is SSRF?

MITRE's CWE-918 describes SSRF as a web server receiving a URL or similar request from an upstream component and retrieving its contents without making sure the request goes to the expected destination. The server becomes a proxy that an outsider can steer, often toward internal systems that outsiders cannot reach directly.

OWASP's SSRF prevention cheat sheet lists common triggers: an avatar image fetched from a user supplied address, a custom webhook, and internal calls between services that include user data. It also notes that SSRF is not limited to HTTP.

Which case are you in?

The OWASP guidance splits the problem in two, and this is the best starting point.

Case 1: your application only needs to talk to known services. Then use an allow list. A webhook that always goes to your payment partner needs one fixed host, not a free form URL. This is the strongest defence because the attacker has nothing to steer.

Case 2: your application must fetch arbitrary external addresses. A link preview or an import by URL feature falls here. An allow list is not possible, so you have to block internal destinations and constrain the request in several layers.

What does a safe Go client do?

Go's standard library gives you the pieces. Concepts, not a full listing:

  1. Parse and check the URL. Use net/url to parse. Accept only https (and http if you must). Reject user info in the URL and unexpected ports.
  2. Check the address that is actually dialled. A hostname can resolve to an internal address, or change its answer between your check and your connection. In net.Dialer, the Control function is called after the network connection is created but before dialling, and it receives the resolved address. Checking there means you validate the real destination, not the name.
  3. Block internal ranges. The net.IP type offers IsLoopback, IsLinkLocalUnicast and IsPrivate. Note the Go documentation warns that IsPrivate does not describe a security property of addresses, so treat it as one input to your own deny logic, not a complete filter. Also deny unspecified and multicast addresses, and the cloud metadata address range, which is link-local.
  4. Control redirects. The http.Client field CheckRedirect is called before each redirect is followed. Use it to re-run your checks on the new target, or to refuse redirects entirely.
  5. Set timeouts and size limits. Use client timeouts and io.LimitReader so a request cannot hang or pull gigabytes.
  6. Do not return raw responses. If the feature only needs an image's dimensions, return those, not the body. Raw output makes probing easy.

A constructed example

A Go service accepts a URL for a profile picture. The first version checks that the hostname does not contain the words "localhost" or "internal". An attacker supplies a hostname they control that resolves to a private address, and the service fetches an internal admin page. A fix that blocks only strings in the name does not help, because the problem is the address.

The revised version parses the URL, allows only https, dials through a Dialer whose Control function rejects loopback, link-local and private ranges, refuses redirects, and returns only the image if its content type is an image.

What outside the code helps?

OWASP lists network layer protections alongside application ones. Run the service in a network segment that cannot reach internal admin systems. Use egress rules that allow only needed outbound traffic. If you run on a cloud provider, require the more protected version of the instance metadata service, which the cheat sheet discusses for AWS, so a simple forged request cannot read credentials.

A deny list on its own is a last resort in the OWASP text. Attackers find forms of an address that filters miss.

How to check or apply this

  1. Search your Go code for http.Get, http.Client and net.Dial calls that take input from users or from stored configuration.
  2. For each, decide whether Case 1 or Case 2 applies, and move Case 1 to a fixed allow list.
  3. Add a shared safe client package, so teams do not each write their own.
  4. Write tests that try loopback, private, link-local and redirecting destinations and expect refusal.
  5. Add egress controls at the network level.
  6. Include static analysis rules for unsafe outbound calls, and review findings by hand.

Quick check

Is validating the hostname enough?

No. A hostname can resolve to an internal address. Check the resolved IP at connect time.

Should I follow redirects?

Only if you need to, and then re-check every hop. Refusing redirects is the safest default.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.