gofiber/fiber · critical · ErrUpstreamHostBlocked
%w: %s -> %s
Error message
%w: %s -> %s
What it means
validateHostForSSRF iterates every resolved address and blocks the request if ANY one is in a blocked range — even if other answers are public. This mixed-answer rejection is intentional: it mitigates DNS-rebinding where a resolver returns a public IP for validation and a private IP at connect time. The message shows host -> blocked-IP.
Source
Thrown at middleware/proxy/security.go:409
if ip := net.ParseIP(host); ip != nil {
if isBlockedIP(ip) {
return fmt.Errorf("%w: %s", ErrUpstreamHostBlocked, ip)
}
return nil
}
// Bound the lookup so a slow resolver cannot stall the caller.
ctx, cancel := context.WithTimeout(context.Background(), dnsLookupTimeout)
defer cancel()
addrs, err := dnsResolver.Load().LookupIPAddr(ctx, host)
if err != nil {
return fmt.Errorf("%w: %s lookup failed: %w", ErrUpstreamHostBlocked, host, err)
}
if len(addrs) == 0 {
return fmt.Errorf("%w: %s has no addresses", ErrUpstreamHostBlocked, host)
}
for _, addr := range addrs {
if isBlockedIP(addr.IP) {
return fmt.Errorf("%w: %s -> %s", ErrUpstreamHostBlocked, host, addr.IP)
}
}
return nil
}
// newSSRFDialer returns a fasthttp DialFunc that resolves the target host
// (with a bounded timeout), rejects the connection if any resolved
// address falls in a blocked range, and then dials a validated address.
// Performing the check at dial time — rather than only up front — defeats
// DNS-rebinding attacks (the check/use gap) where a resolver returns a
// public address during validation and a private one at connect time. It
// is only installed when the active policy disallows private IPs.
//
//nolint:revive // dialDualStack mirrors fasthttp.HostClient.DialDualStack
func newSSRFDialer(dialDualStack bool) fasthttp.DialFunc {
dialer := &net.Dialer{Timeout: dnsLookupTimeout}
return func(addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)View on GitHub (pinned to a105acad6c)
Solutions
- Use a hostname whose DNS answers are all public IPs if AllowPrivateIPs is false.
- If the target is an approved internal service, set SecurityPolicy.AllowPrivateIPs: true via WithSecurityPolicy and document the SSRF acceptance.
- For user-supplied URLs, run your own DNS validation and reject mixed-answer or private-containing responses before calling proxy.Do.
- Pin to a specific validated IP if your architecture allows (the dial-time guard will still re-validate).
- Audit which hostnames are expected to resolve to private ranges and ensure they go through an opt-in policy.
Example fix
// before: hostname mixes public + private, blocked
WithSecurityPolicy(DefaultSecurityPolicy()) // AllowPrivateIPs: false
proxy.Do(ctx, "http://internal.svc")
// after: deliberate opt-in for approved internal target
WithSecurityPolicy(SecurityPolicy{AllowPrivateIPs: true})
proxy.Do(ctx, "http://internal.svc") Defensive patterns
Strategy: validation
Validate before calling
// Reject hostnames that resolve to any blocked address.
func allPublic(host string) error {
ips, err := net.LookupIP(host); if err != nil { return err }
for _, ip := range ips { if isBlocked(ip) { return fmt.Errorf("%s -> %s blocked", host, ip) } }
return nil
} Prevention
- Keep AllowPrivateIPs false; the mixed-answer rejection is intentional rebinding defense.
- For user URLs, run your own allPublic-style check before proxying.
- Document any hostname expected to resolve privately and route through opt-in policy.
- Alert on unexpected occurrences.
When it happens
Trigger: A hostname resolves to a mix of public and private IPs (classic rebinding setup); a hostname that legitimately has both a public and a private view (split-horizon DNS seen by the same resolver); an internal hostname whose only records are private; a hostname resolving to 127.0.0.1, 169.254.x.x, or a CGNAT address.
Common situations: Adversarial DNS configured to flip answers; an internal service whose DNS legitimately returns RFC1918 addresses; cloud-provider internal hostnames; IPv6 transition-range addresses (6to4, Teredo, NAT64-local).
Related errors
- proxy: upstream scheme is not allowed
- ErrUpstreamHostBlocked
- ErrUpstreamSchemeNotAllowed
- ErrUpstreamHostBlocked
- ErrRedirectDowngrade
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/471060b967fac711.
Report an issue: GitHub.