{"record":{"id":"cc09d98e2a9d5174","repo":"JuliusBrussee/caveman","slug":"ssrf-scheme-q-not-permitted-managed-mode-requir","errorCode":null,"errorMessage":"ssrf: scheme %q not permitted (managed mode requires https)","messagePattern":"ssrf: scheme %q not permitted \\(managed mode requires https\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/ssrf/ssrf.go","lineNumber":216,"sourceCode":"\t\treturn checkAddr(addr, host, port, cfg)\n\t}\n\tif strings.EqualFold(host, \"localhost\") && !(!cfg.ManagedMode && isInAllowList(host, port, cfg.AllowList)) {\n\t\treturn fmt.Errorf(\"ssrf: host %q is blocked (loopback)\", host)\n\t}\n\treturn nil\n}\n\n// parseValidatedURL applies the URL-shape policy — scheme, embedded\n// credentials, managed-mode port — and returns the host/port to check.\nfunc parseValidatedURL(raw string, cfg Config) (host, port string, err error) {\n\tu, parseErr := url.Parse(raw)\n\tif parseErr != nil {\n\t\t// net/url.Error includes the raw URL (and may therefore include\n\t\t// credentials or query secrets). Keep this error field-only and stable.\n\t\treturn \"\", \"\", errors.New(\"ssrf: invalid URL\")\n\t}\n\tif u.Scheme != \"https\" && !(u.Scheme == \"http\" && !cfg.ManagedMode) {\n\t\treturn \"\", \"\", fmt.Errorf(\"ssrf: scheme %q not permitted (managed mode requires https)\", u.Scheme)\n\t}\n\tif u.User != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"ssrf: credentials embedded in URL are forbidden\")\n\t}\n\thost = u.Hostname()\n\tif host == \"\" {\n\t\treturn \"\", \"\", fmt.Errorf(\"ssrf: URL must contain a host\")\n\t}\n\tport = u.Port()\n\tif cfg.ManagedMode && port != \"\" && port != \"443\" {\n\t\treturn \"\", \"\", errors.New(\"ssrf: managed mode requires port 443\")\n\t}\n\tif port == \"\" {\n\t\tif u.Scheme == \"https\" {\n\t\t\tport = \"443\"\n\t\t} else {\n\t\t\tport = \"80\"\n\t\t}","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/ssrf/ssrf.go#L198-L234","documentation":"This error comes from the SSRF guard's URL-shape policy in parseValidatedURL. Only https is accepted, except that plain http is tolerated when the caller built the Config with ManagedMode=false (self-hosted). The library enforces TLS-only egress in managed (multi-tenant prod) mode so requests cannot be downgraded or sent in cleartext.","triggerScenarios":"Calling ssrf.ValidateURL or ssrf.ValidateURLNoResolve with a URL whose scheme is not https or http — e.g. file://, ftp://, gopher:// — or with an http:// URL while Config.ManagedMode is true.","commonSituations":"A provider base URL configured as http://localhost:8080 for local development is promoted to a managed-mode deployment; a user-supplied callback/webhook URL using file:// or another exotic scheme; an environment variable defaulting to http:// instead of https://.","solutions":["Change the URL to use https (and port 443 in managed mode)","If this is a self-hosted deployment that legitimately needs http, construct the Config with ssrf.SelfHostedConfig(...) so ManagedMode is false","If the scheme is user-controlled, reject non-https input before handing it to the library","Check the CAVE_ENV / managed-mode wiring: if you did not intend managed mode, do not use ssrf.ManagedConfig()"],"exampleFix":"// before\ncfg := ssrf.ManagedConfig()\nerr := ssrf.ValidateURL(ctx, \"http://api.example.com/v1\", cfg)\n\n// after\ncfg := ssrf.ManagedConfig()\nerr := ssrf.ValidateURL(ctx, \"https://api.example.com/v1\", cfg)","handlingStrategy":"validation","validationCode":"u, err := url.Parse(raw)\nif err != nil || (u.Scheme != \"https\" && !(u.Scheme == \"http\" && !managed)) {\n    return fmt.Errorf(\"endpoint must use https in managed mode: got %q scheme\", schemeOf(raw))\n}","typeGuard":"func isHTTPSEndpoint(raw string) bool {\n    u, err := url.Parse(raw)\n    return err == nil && u.Scheme == \"https\"\n}","tryCatchPattern":"if err := ssrf.ValidateURL(ctx, endpoint, cfg); err != nil {\n    var schemeErr bool\n    if strings.Contains(err.Error(), \"not permitted\") && strings.Contains(err.Error(), \"scheme\") {\n        schemeErr = true // fail fast with config guidance\n    }\n    return fmt.Errorf(\"endpoint config invalid (schemeErr=%v): %w\", schemeErr, err)\n}","preventionTips":["Default every configured base URL to https:// in config templates","Add a startup assertion that all outbound endpoints parse as https","Keep dev http:// URLs in self-hosted config only; never copy them into managed-mode config"],"tags":["ssrf","security","url-validation","tls"],"backgroundTag":"invalid-url","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}