{"record":{"id":"572892170ea8f795","repo":"JuliusBrussee/caveman","slug":"ssrf-credentials-embedded-in-url-are-forbidden","errorCode":null,"errorMessage":"ssrf: credentials embedded in URL are forbidden","messagePattern":"ssrf: credentials embedded in URL are forbidden","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/ssrf/ssrf.go","lineNumber":219,"sourceCode":"\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}\n\t}\n\treturn host, port, nil\n}","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/ssrf/ssrf.go#L201-L237","documentation":"parseValidatedURL rejects any URL that carries a userinfo component (user:pass@host). Credentials in URLs leak into logs, proxies, and error messages, so this library flatly forbids them instead of passing them through.","triggerScenarios":"Calling ssrf.ValidateURL / ValidateURLNoResolve with a URL like https://user:token@api.example.com — i.e. url.URL.User is non-nil after parsing.","commonSituations":"Copying a provider endpoint from docs or a curl example that embeds an API key in the URL; migrating from a service that accepted basic-auth-in-URL; templating a base URL with credentials interpolated.","solutions":["Remove the userinfo from the URL and send credentials via headers (e.g. Authorization: Bearer) or the request body instead","If credentials come from config, move them to a dedicated env var / secret field and build the request in code","Pre-validate URLs and reject any containing '@' before the userinfo part"],"exampleFix":"// before\nendpoint := \"https://user:sk-123@api.example.com\"\nreq, _ := http.NewRequest(\"POST\", endpoint, body)\nreq.SetBasicAuth(\"user\", \"sk-123\")\n\n// after\nendpoint := \"https://api.example.com\"\nreq, _ := http.NewRequest(\"POST\", endpoint, body)\nreq.SetBasicAuth(\"user\", \"sk-123\")","handlingStrategy":"validation","validationCode":"if u, err := url.Parse(raw); err != nil || u.User != nil {\n    return errors.New(\"URL must not contain embedded credentials; use headers instead\")\n}","typeGuard":"func hasURLCredentials(raw string) bool {\n    u, err := url.Parse(raw)\n    return err == nil && u.User != nil\n}","tryCatchPattern":"if err := ssrf.ValidateURL(ctx, endpoint, cfg); err != nil {\n    if strings.Contains(err.Error(), \"credentials embedded in URL\") {\n        return fmt.Errorf(\"strip userinfo from endpoint %q and pass it via Authorization header\", redactHost(endpoint))\n    }\n    return err\n}","preventionTips":["Never paste curl -u examples into base-URL config","Store API keys in env vars/secrets, never in URLs","Lint config files for '@' inside URL authority sections"],"tags":["ssrf","security","url-validation","credentials"],"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"}