{"record":{"id":"471060b967fac711","repo":"gofiber/fiber","slug":"w-s-s","errorCode":null,"errorMessage":"%w: %s -> %s","messagePattern":"%w: (.+?) -> (.+?)","errorType":"validation","errorClass":"ErrUpstreamHostBlocked","httpStatus":null,"severity":"critical","filePath":"middleware/proxy/security.go","lineNumber":409,"sourceCode":"\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\nfunc newSSRFDialer(dialDualStack bool) fasthttp.DialFunc {\n\tdialer := &net.Dialer{Timeout: dnsLookupTimeout}\n\treturn func(addr string) (net.Conn, error) {\n\t\thost, port, err := net.SplitHostPort(addr)","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L391-L427","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before: hostname mixes public + private, blocked\nWithSecurityPolicy(DefaultSecurityPolicy()) // AllowPrivateIPs: false\nproxy.Do(ctx, \"http://internal.svc\")\n\n// after: deliberate opt-in for approved internal target\nWithSecurityPolicy(SecurityPolicy{AllowPrivateIPs: true})\nproxy.Do(ctx, \"http://internal.svc\")","handlingStrategy":"validation","validationCode":"// Reject hostnames that resolve to any blocked address.\nfunc allPublic(host string) error {\n  ips, err := net.LookupIP(host); if err != nil { return err }\n  for _, ip := range ips { if isBlocked(ip) { return fmt.Errorf(\"%s -> %s blocked\", host, ip) } }\n  return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["proxy","ssrf","dns-rebinding","ip-block","security"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}