{"record":{"id":"cc5c5914265c7dc0","repo":"gofiber/fiber","slug":"w-s","errorCode":null,"errorMessage":"%w: %s","messagePattern":"%w: %s","errorType":"validation","errorClass":"ErrUpstreamHostBlocked","httpStatus":null,"severity":"critical","filePath":"middleware/proxy/security.go","lineNumber":351,"sourceCode":"}\n\n// validateUpstreamForBalancer validates a statically configured Balancer\n// upstream. It enforces the scheme allowlist and rejects IP-literal hosts\n// in blocked ranges, but defers hostname resolution to the SSRF-guarded\n// dialer (see newSSRFDialer). Deferring DNS keeps a transient resolver\n// failure at startup from panicking the application (e.g. crash loops in\n// container orchestrators) and re-checks the resolved IP on every dial,\n// which also defeats DNS-rebinding.\nfunc validateUpstreamForBalancer(raw string, policy SecurityPolicy) (*url.URL, error) {\n\tu, err := parseUpstreamScheme(raw, policy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif policy.AllowPrivateIPs {\n\t\treturn u, nil\n\t}\n\tif ip := net.ParseIP(trimBrackets(u.Hostname())); ip != nil && isBlockedIP(ip) {\n\t\treturn nil, fmt.Errorf(\"%w: %s\", ErrUpstreamHostBlocked, ip)\n\t}\n\treturn u, nil\n}\n\n// schemeAllowed reports whether scheme is on the allowlist. An empty\n// allowlist falls back to the secure defaults.\nfunc schemeAllowed(scheme string, allowed []string) bool {\n\tif scheme == \"\" {\n\t\treturn false\n\t}\n\tif len(allowed) == 0 {\n\t\tallowed = defaultAllowedSchemes\n\t}\n\tfor _, s := range allowed {\n\t\tif utils.EqualFold(s, scheme) {\n\t\t\treturn true\n\t\t}\n\t}","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L333-L369","documentation":"validateUpstreamForBalancer statically checks IP-literal upstream hosts at Balancer construction time. If the host is a literal IP in a blocked range (loopback, RFC1918, link-local, multicast, unspecified, CGNAT 100.64/10, or blocked IPv6 transition ranges) and AllowPrivateIPs is false, this error fires. Hostname resolution is deferred to the dial-time guard, so this only catches literal IPs.","triggerScenarios":"Balancer config includes an upstream like http://127.0.0.1, http://10.0.0.5, http://169.254.169.254 (cloud metadata), http://100.64.0.1 (CGNAT), or an IPv6 literal in a transition range. Triggered once at startup/config-reload.","commonSituations":"Local dev pointing a Balancer at localhost; misconfigured service discovery returning a private IP; an attempt to reach a cloud metadata endpoint through the proxy; IPv6 6to4/Teredo/NAT64-local addresses.","solutions":["Point the upstream at a public IP or hostname that resolves publicly.","If the target is legitimately internal (e.g. sidecar in the same pod), explicitly opt in via SecurityPolicy{AllowPrivateIPs: true} and document the SSRF exposure this introduces.","Use a DNS name rather than a literal IP so the dial-time guard can re-check the resolved address on every connect (defeats rebinding).","Audit the Balancer.Servers slice at config load to catch blocked literals before runtime."],"exampleFix":"// before: literal private IP blocked at startup\nbalancer.Servers = []string{\"http://10.0.0.5:8080\"}\n\n// after: explicit opt-in for an internal sidecar\nWithSecurityPolicy(DefaultSecurityPolicy()) // AllowPrivateIPs: true","handlingStrategy":"validation","validationCode":"func validateBalancerUpstream(raw string, policy proxy.SecurityPolicy) error {\n  u, err := url.Parse(strings.TrimSpace(raw))\n  if err != nil { return err }\n  if ip := net.ParseIP(strings.Trim(u.Hostname(), \"[]\")); ip != nil && !policy.AllowPrivateIPs {\n    // replicate isBlockedIP or rely on validateUpstreamForBalancer at construction\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep AllowPrivateIPs false in production.","Use DNS names (not literal IPs) for upstreams so the dial-time guard can re-check.","Audit Balancer.Servers for private/literal IPs at config load.","Document any deliberate opt-in to private IPs."],"tags":["proxy","ssrf","balancer","security-policy","private-ip"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}