{"record":{"id":"47335b7579cfd67b","repo":"gofiber/fiber","slug":"proxy-parse-upstream-q-w","errorCode":null,"errorMessage":"proxy: parse upstream %q: %w","messagePattern":"proxy: parse upstream %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/proxy/security.go","lineNumber":293,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n}\n\n// parseUpstream returns the parsed url.URL for raw. Hosts without an\n// explicit scheme default to http:// to match the historical Balancer\n// behavior where bare \"host:port\" entries were accepted.\nfunc parseUpstream(raw string) (*url.URL, error) {\n\traw = utils.TrimSpace(raw)\n\tif raw == \"\" {\n\t\treturn nil, ErrUpstreamHostInvalid\n\t}\n\tif !strings.Contains(raw, \"://\") {\n\t\traw = \"http://\" + raw\n\t}\n\tu, err := url.Parse(raw)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"proxy: parse upstream %q: %w\", raw, err)\n\t}\n\treturn u, nil\n}\n\n// validateUpstream parses raw, enforces the scheme allowlist, and unless\n// the policy permits private addresses, resolves the hostname and\n// rejects responses that include any blocked address. Rejecting on a\n// single blocked answer mitigates DNS rebinding attempts in which the\n// resolver returns a mix of public and private IPs.\nfunc validateUpstream(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 err := validateHostForSSRF(u.Hostname()); err != nil {","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L275-L311","documentation":"parseUpstream trims and normalizes the raw upstream string (defaulting to http:// if no scheme) then calls url.Parse. If url.Parse returns an error — typically for control characters, invalid percent-escapes, or malformed URLs — it is wrapped with the normalized raw value for diagnosis. This is the lowest-level parse failure, before scheme/host checks.","triggerScenarios":"Configured upstream contains raw control bytes (e.g. a CR/LF injection in a config file), invalid percent-encoding like '%zz', unescaped spaces, or a stray bracket. Also triggered by user input forwarded as an upstream target without sanitization.","commonSituations":"Misformatted Balancer.Servers or proxy.Targets entry in config; env var with trailing whitespace or quotes that survive into url.Parse; a config-management tool that wrote a non-printable character; copy-paste from a rich-text source introducing smart quotes.","solutions":["Inspect the quoted raw value in the message: it shows exactly what was parsed after trimming and scheme defaulting.","Re-enter the upstream string as a clean ASCII value with no spaces or control characters.","Validate upstreams at config load time and fail fast with a clear message rather than at request time.","If upstreams come from user input, sanitize (reject control bytes, require valid percent-encoding) before passing to proxy.","URL-encode any path components that legitimately contain special characters."],"exampleFix":"// before: trailing space or control char in env\nupstream := os.Getenv(\"UPSTREAM\") // \"http://svc:8080\\r\"\n\n// after: trim and validate\nupstream := strings.TrimSpace(os.Getenv(\"UPSTREAM\"))\nif _, err := url.Parse(upstream); err != nil { log.Fatalf(\"bad UPSTREAM: %v\", err) }","handlingStrategy":"validation","validationCode":"// Validate upstream strings at config load.\nfunc validUpstream(raw string) error {\n  if strings.TrimSpace(raw) == \"\" { return errors.New(\"empty upstream\") }\n  if !strings.Contains(raw, \"://\") { raw = \"http://\" + raw }\n  _, err := url.Parse(raw)\n  return err\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim and parse every upstream at config load; fail fast.","Source upstreams from env with care (no trailing whitespace/quotes).","Reject control bytes in any user-supplied URL before passing to proxy.","Use ASCII-only config sources."],"tags":["proxy","url-parse","config","balancer"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}