{"record":{"id":"c0616095189a5839","repo":"gofiber/fiber","slug":"w-q","errorCode":null,"errorMessage":"%w: %q","messagePattern":"%w: %q","errorType":"validation","errorClass":"ErrUpstreamSchemeNotAllowed","httpStatus":null,"severity":"error","filePath":"middleware/proxy/security.go","lineNumber":327,"sourceCode":"\t\treturn u, nil\n\t}\n\tif err := validateHostForSSRF(u.Hostname()); err != nil {\n\t\treturn nil, err\n\t}\n\treturn u, nil\n}\n\n// parseUpstreamScheme parses raw and enforces the scheme allowlist and\n// host presence without performing any DNS resolution. url.Parse can\n// leave Host set while Hostname() is empty (e.g. \"http://:8080\"), so the\n// presence check uses Hostname.\nfunc parseUpstreamScheme(raw string, policy SecurityPolicy) (*url.URL, error) {\n\tu, err := parseUpstream(raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !schemeAllowed(u.Scheme, policy.AllowedSchemes) {\n\t\treturn nil, fmt.Errorf(\"%w: %q\", ErrUpstreamSchemeNotAllowed, u.Scheme)\n\t}\n\tif u.Hostname() == \"\" {\n\t\treturn nil, ErrUpstreamHostInvalid\n\t}\n\treturn u, nil\n}\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","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L309-L345","documentation":"parseUpstreamScheme runs after a successful parseUpstream and rejects any scheme not in the policy allowlist (default http, https). The sentinel is ErrUpstreamSchemeNotAllowed, and the offending scheme is quoted. This is the central scheme gate consulted by both runtime helpers and Balancer construction.","triggerScenarios":"Configuring an upstream with scheme file://, ftp://, gopher://, ws://, or any custom scheme not present in SecurityPolicy.AllowedSchemes. Also when scheme normalization failed (e.g. uppercase HTTPS slipped through if you narrowed the allowlist case-sensitively — though schemeAllowed uses EqualFold).","commonSituations":"Operator expects file:// to work for local proxying; legacy config with ws:// for a websocket service that should be proxied via http(s) upgrade instead; a SecurityPolicy explicitly narrowed to only https while a config still has an http:// entry; misordered env interpolation producing an empty scheme.","solutions":["Change the upstream to use http:// or https:// (the defaults), or another scheme you have deliberately added to SecurityPolicy.AllowedSchemes.","For websocket traffic, use http(s):// and let fiber/proxy handle the Upgrade header — do not use ws://.","If you genuinely need an additional scheme, pass it via Config.SecurityPolicy.AllowedSchemes = []string{\"http\",\"https\",\"foo\"} (and document the security implications).","Audit all Balancer.Servers, proxy.Targets, and WithSecurityPolicy call sites for the rejected scheme.","Print SecurityPolicy.AllowedSchemes at startup to confirm what is permitted."],"exampleFix":"// before: disallowed scheme\nbalancer.Servers = []string{\"ws://upstream:8080\"}\n\n// after: http with Upgrade handled by the upstream\nbalancer.Servers = []string{\"http://upstream:8080\"}","handlingStrategy":"validation","validationCode":"func allowedScheme(raw string, policy proxy.SecurityPolicy) error {\n  u, err := url.Parse(strings.TrimSpace(raw))\n  if err != nil { return err }\n  allowed := policy.AllowedSchemes\n  if len(allowed) == 0 { allowed = []string{\"http\",\"https\"} }\n  for _, s := range allowed { if strings.EqualFold(s, u.Scheme) { return nil } }\n  return fmt.Errorf(\"scheme %q not allowed\", u.Scheme)\n}","typeGuard":null,"tryCatchPattern":"if err := balancer.Build(); err != nil {\n  if errors.Is(err, proxy.ErrUpstreamSchemeNotAllowed) { /* fix config */ }\n}","preventionTips":["Standardize on http/https for upstreams.","For websockets use http(s) and let the Upgrade flow handle it.","Print the effective AllowedSchemes at startup.","Audit config for non-http(s) schemes before deploy."],"tags":["proxy","ssrf","scheme","security-policy","config"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}