{"record":{"id":"f4b72a331b69c84d","repo":"gofiber/fiber","slug":"proxy-upstream-scheme-is-not-allowed","errorCode":null,"errorMessage":"proxy: upstream scheme is not allowed","messagePattern":"proxy: upstream scheme is not allowed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/proxy/security.go","lineNumber":51,"sourceCode":"// defaultAllowedSchemes is the internal, read-only allowlist used as the\n// fallback inside schemeAllowed when a policy carries no AllowedSchemes.\n// It is never handed out by reference: DefaultSecurityPolicy() and\n// normalizePolicy() copy it before it can reach the exported\n// SecurityPolicy.AllowedSchemes field, so nothing outside this file can\n// mutate the backing array.\nvar defaultAllowedSchemes = []string{schemeHTTP, schemeHTTPS}\n\n// httpsSchemeBytes is the byte form of \"https\" used by redirect\n// downgrade checks. Stored once so the resolveRedirect hot path doesn't\n// allocate []byte(\"https\") on every hop.\nvar httpsSchemeBytes = []byte(schemeHTTPS)\n\n// Sentinel errors returned when an upstream target violates the configured\n// proxy security policy.\nvar (\n\t// ErrUpstreamSchemeNotAllowed is returned when the proxied URL uses a\n\t// scheme outside the configured allowlist (default: http, https).\n\tErrUpstreamSchemeNotAllowed = errors.New(\"proxy: upstream scheme is not allowed\")\n\n\t// ErrUpstreamHostInvalid is returned when the proxied URL is missing a\n\t// host or cannot be parsed.\n\tErrUpstreamHostInvalid = errors.New(\"proxy: upstream host is empty or invalid\")\n\n\t// ErrUpstreamHostBlocked is returned when the proxied URL resolves to\n\t// an address inside a blocked range (loopback, RFC 1918 private,\n\t// link-local, multicast, unspecified, or CGNAT) and AllowPrivateIPs\n\t// is false.\n\tErrUpstreamHostBlocked = errors.New(\"proxy: upstream host resolves to a blocked address\")\n\n\t// ErrRedirectDowngrade is returned when DoRedirects encounters a\n\t// redirect from an HTTPS upstream to a plaintext HTTP target and\n\t// AllowHTTPSDowngrade is false.\n\tErrRedirectDowngrade = errors.New(\"proxy: HTTPS to HTTP redirect blocked\")\n)\n\n// SecurityPolicy controls runtime security restrictions applied to the","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/security.go#L33-L69","documentation":"The proxy middleware rejects any upstream URL whose scheme is not in SecurityPolicy.AllowedSchemes (defaults to {http, https} per defaultAllowedSchemes). This is a primary SSRF defense: it prevents the proxy from being abused to fetch via file://, gopher://, ftp://, or other dangerous schemes. The error is returned by proxy.Do/Forward/DoRedirects/DoTimeout/DoDeadline whenever the resolved target URL's scheme is outside the allowlist.","triggerScenarios":"Calling proxy.Do(c, targetURL) where targetURL has a scheme other than http/https (e.g., file:///etc/passwd, gopher://, ftp://). Also triggered if you set SecurityPolicy.AllowedSchemes to a custom list and the target uses a scheme not in that list.","commonSituations":"Handlers that proxy user-supplied URLs without validation; misconfigured upstream targets with wrong scheme typos; SSRF attack payloads hitting a public proxy endpoint; switching from http to a custom protocol without updating AllowedSchemes.","solutions":["Validate the target URL scheme against {http, https} in the handler before calling proxy.Do/Forward/etc.","If a non-default scheme is legitimately needed, populate SecurityPolicy.AllowedSchemes with the exact allowed set and pass the policy via Config.SecurityPolicy.","Reject user-controlled target URLs at the trust boundary; never pass raw user input to proxy helpers.","Log the offending scheme to detect probing/abuse attempts."],"exampleFix":"// before\napp.Get(\"/proxy\", func(c fiber.Ctx) error {\n    return proxy.Do(c, c.Query(\"url\"))\n})\n\n// after\napp.Get(\"/proxy\", func(c fiber.Ctx) error {\n    u, err := url.Parse(c.Query(\"url\"))\n    if err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n        return fiber.NewError(fiber.StatusBadRequest, \"invalid target URL\")\n    }\n    return proxy.Do(c, u.String())\n})","handlingStrategy":"validation","validationCode":"u, err := url.Parse(target)\nif err != nil {\n    return fiber.NewError(fiber.StatusBadRequest, \"invalid target URL\")\n}\nallowed := map[string]bool{\"http\": true, \"https\": true}\nif cfg, ok := policy.(*proxy.SecurityPolicy); ok && len(cfg.AllowedSchemes) > 0 {\n    allowed = make(map[string]bool, len(cfg.AllowedSchemes))\n    for _, s := range cfg.AllowedSchemes { allowed[s] = true }\n}\nif !allowed[u.Scheme] {\n    return fiber.NewError(fiber.StatusBadRequest, \"scheme not permitted\")\n}","typeGuard":"func isAllowedScheme(u *url.URL, allowed []string) bool {\n    set := map[string]bool{\"http\": true, \"https\": true}\n    for _, s := range allowed { set[s] = true }\n    return set[u.Scheme]\n}","tryCatchPattern":null,"preventionTips":["Never pass raw user input as the proxy target; parse and allowlist the scheme first.","Document the project's allowed schemes in CLAUDE.md/AGENTS.md exposure posture.","Treat any proxy endpoint as public and SSRF-prone until proven otherwise."],"tags":["proxy","security","ssrf","url-validation","scheme"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}