Input Validation and SSRF

Updated

September 8, 2026

Input Validation and SSRF

Overview

Validate at trust boundaries. SSRF (server-side request forgery) happens when user-controlled URLs make your server fetch internal resources (169.254.169.254, localhost, cloud metadata).

Validation basics

type CreateBook struct {
    Title string `json:"title"`
    Price int    `json:"price_cents"`
}

func (c CreateBook) Valid() error {
    if len(c.Title) == 0 || len(c.Title) > 200 {
        return fmt.Errorf("title length")
    }
    if c.Price < 0 {
        return fmt.Errorf("price")
    }
    return nil
}
  • Prefer allowlists over blocklists for enums
  • Bound string lengths and collection sizes
  • Parse numbers with bit size (ParseInt(..., 10, 64))

Path traversal

// never: os.Open("/data/" + userPath)
// use safeJoin from filepath chapter

SSRF defenses

When your app fetches a URL on behalf of a user:

  1. Allowlist schemes (https only)
  2. Allowlist hosts or resolve and block private ranges
  3. No redirects to off-allowlist targets
  4. Timeouts + size limits
func safeURL(raw string) (*url.URL, error) {
    u, err := url.Parse(raw)
    if err != nil {
        return nil, err
    }
    if u.Scheme != "https" {
        return nil, fmt.Errorf("scheme")
    }
    host := u.Hostname()
    ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
    // reject loopback, link-local, private RFC1918, metadata IP
    return u, nil
}
func isBadIP(ip net.IP) bool {
    return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
        ip.IsLinkLocalMulticast() || ip.Equal(net.ParseIP("169.254.169.254"))
}

Re-check IPs after redirects (pin or disable redirects).

Header injection

Don’t put raw user strings into Header.Set without validation (CRLF). Prefer structured values.

Rules of thumb

Do Don’t
Validate then use Trust client JSON shapes unbounded
Allowlist outbound hosts Open proxy to any URL
Bound body size Stream forever into memory

Try next

  1. Reject http://127.0.0.1/ in a fetch helper.
  2. Reject path ../../etc/passwd in download handler.
  3. Fuzz validators with go test -fuzz.