{"record":{"id":"80311056482ba5fa","repo":"gofiber/fiber","slug":"w-s-has-no-addresses","errorCode":null,"errorMessage":"%w: %s has no addresses","messagePattern":"%w: (.+?) has no addresses","errorType":"validation","errorClass":"ErrUpstreamHostBlocked","httpStatus":null,"severity":"error","filePath":"middleware/proxy/security.go","lineNumber":405,"sourceCode":"\t}\n\t// strip brackets from IPv6 literals (url.Hostname already does this\n\t// in most cases, but keep the guard for defensive callers).\n\thost = trimBrackets(host)\n\tif ip := net.ParseIP(host); ip != nil {\n\t\tif isBlockedIP(ip) {\n\t\t\treturn fmt.Errorf(\"%w: %s\", ErrUpstreamHostBlocked, ip)\n\t\t}\n\t\treturn nil\n\t}\n\t// Bound the lookup so a slow resolver cannot stall the caller.\n\tctx, cancel := context.WithTimeout(context.Background(), dnsLookupTimeout)\n\tdefer cancel()\n\taddrs, err := dnsResolver.Load().LookupIPAddr(ctx, host)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: %s lookup failed: %w\", ErrUpstreamHostBlocked, host, err)\n\t}\n\tif len(addrs) == 0 {\n\t\treturn fmt.Errorf(\"%w: %s has no addresses\", ErrUpstreamHostBlocked, host)\n\t}\n\tfor _, addr := range addrs {\n\t\tif isBlockedIP(addr.IP) {\n\t\t\treturn fmt.Errorf(\"%w: %s -> %s\", ErrUpstreamHostBlocked, host, addr.IP)\n\t\t}\n\t}\n\treturn nil\n}\n\n// newSSRFDialer returns a fasthttp DialFunc that resolves the target host\n// (with a bounded timeout), rejects the connection if any resolved\n// address falls in a blocked range, and then dials a validated address.\n// Performing the check at dial time — rather than only up front — defeats\n// DNS-rebinding attacks (the check/use gap) where a resolver returns a\n// public address during validation and a private one at connect time. It\n// is only installed when the active policy disallows private IPs.\n//\n//nolint:revive // dialDualStack mirrors fasthttp.HostClient.DialDualStack","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L387-L423","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before: hostname with no A record\nproxy.Do(ctx, \"http://empty.example.com\")\n\n// after: confirm records before relying on the host\n// $ dig A empty.example.com +short  -> must return at least one IP","handlingStrategy":"validation","validationCode":"func hasRecords(host string) bool {\n  ips, err := net.LookupIP(host)\n  return err == nil && len(ips) > 0\n}","typeGuard":null,"tryCatchPattern":"if err := proxy.Do(c); err != nil {\n  if errors.Is(err, proxy.ErrUpstreamHostBlocked) && strings.Contains(err.Error(), \"no addresses\") { return c.Status(502) }\n}","preventionTips":["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."],"tags":["proxy","ssrf","dns","resolution","empty-answer"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}