gofiber/fiber · error · ErrUpstreamHostBlocked
%w: %s has no addresses
Error message
%w: %s has no addresses
What it means
validateHostForSSRF treats an empty address list from a successful LookupIPAddr as blocked (ErrUpstreamHostBlocked). This catches resolvers that return success with zero A/AAAA records — a condition distinct from a lookup error and one that can mask a misconfigured DNS backend.
Source
Thrown at middleware/proxy/security.go:405
}
// strip brackets from IPv6 literals (url.Hostname already does this
// in most cases, but keep the guard for defensive callers).
host = trimBrackets(host)
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.DialDualStackView on GitHub (pinned to a105acad6c)
Solutions
- Confirm the hostname has A/AAAA records: dig A hostname +short and dig AAAA hostname +short.
- If the target is an internal service, ensure the in-cluster/VPC resolver returns real records, not just a CNAME chain with no terminal A.
- Treat this as a 502 to the client and alert on it — an empty answer set is usually a misconfiguration.
- Retry once to rule out a transient resolver hiccup; if persistent, escalate to the DNS/infra owner.
- Verify the resolver the Go binary uses is the one you think (check net.Dialer.Resolver overrides).
Example fix
// before: hostname with no A record proxy.Do(ctx, "http://empty.example.com") // after: confirm records before relying on the host // $ dig A empty.example.com +short -> must return at least one IP
Defensive patterns
Strategy: validation
Validate before calling
func hasRecords(host string) bool {
ips, err := net.LookupIP(host)
return err == nil && len(ips) > 0
} Try / catch
if err := proxy.Do(c); err != nil {
if errors.Is(err, proxy.ErrUpstreamHostBlocked) && strings.Contains(err.Error(), "no addresses") { return c.Status(502) }
} Prevention
- Ensure upstream hostnames have A/AAAA records.
- Treat empty DNS answers as a misconfiguration and alert.
- Retry once for transient empty-answer responses.
- Verify the resolver in use is the one you expect.
When it happens
Trigger: Hostname exists in DNS but has no A or AAAA records (e.g. only MX or TXT records); split-horizon DNS that returns an empty answer for the app's view; a wildcard record configured with no records; an internal resolver returning NOERROR with no answers.
Common situations: Service discovery entry created without an A record; recently created hostname still propagating; DNS load-balancer that occasionally returns empty answer sets; misconfigured external DNS for a vendor hostname.
Related errors
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/80311056482ba5fa.
Report an issue: GitHub.