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.DialDualStack

View on GitHub (pinned to a105acad6c)

Solutions

  1. Confirm the hostname has A/AAAA records: dig A hostname +short and dig AAAA hostname +short.
  2. 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.
  3. Treat this as a 502 to the client and alert on it — an empty answer set is usually a misconfiguration.
  4. Retry once to rule out a transient resolver hiccup; if persistent, escalate to the DNS/infra owner.
  5. 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

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.