JuliusBrussee/caveman · error
ssrf: credentials embedded in URL are forbidden
Error message
ssrf: credentials embedded in URL are forbidden
What it means
ssrf.ValidateURL rejects URLs containing userinfo (u.User != nil), i.e. https://user:pass@host/... style URLs. Embedded credentials leak into logs, referrers, and error messages, so the library forbids them outright regardless of mode.
Source
Thrown at shared/platform/ssrf/ssrf.go:170
// ValidateURL resolves raw to a URL, validates the scheme/port constraints,
// and checks every IP the hostname resolves to against the SSRF block lists.
// It is a pre-flight check only — see NewDialContext for dial-time enforcement.
//
// Errors are safe to return to callers; they contain the blocked IP but never
// the original credential material.
func ValidateURL(ctx context.Context, raw string, cfg Config) error {
u, err := url.Parse(raw)
if err != nil {
// net/url.Error includes the raw URL (and may therefore include
// credentials or query secrets). Keep this error field-only and stable.
return errors.New("ssrf: invalid URL")
}
if u.Scheme != "https" && !(u.Scheme == "http" && !cfg.ManagedMode) {
return fmt.Errorf("ssrf: scheme %q not permitted (managed mode requires https)", u.Scheme)
}
if u.User != nil {
return fmt.Errorf("ssrf: credentials embedded in URL are forbidden")
}
host := u.Hostname()
if host == "" {
return fmt.Errorf("ssrf: URL must contain a host")
}
port := u.Port()
if cfg.ManagedMode && port != "" && port != "443" {
return errors.New("ssrf: managed mode requires port 443")
}
if port == "" {
if u.Scheme == "https" {
port = "443"
} else {
port = "80"
}
}
return validateHostPort(ctx, host, port, cfg)
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Move the credential to a header (Authorization: Bearer/Basic) or the API's documented auth parameter.
- Strip userinfo before persisting URLs: parse with net/url, set u.User = nil, re-serialize.
- Add ingestion-time validation so credential-bearing URLs never reach storage.
Example fix
// before
raw := "https://" + apiKey + "@api.example.com/v1"
err := ssrf.ValidateURL(ctx, raw, cfg) // rejected
// after
req, _ := http.NewRequest("GET", "https://api.example.com/v1", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
err := ssrf.ValidateURL(ctx, "https://api.example.com/v1", cfg) Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(raw)
if err == nil && u.User != nil {
return fmt.Errorf("credentials in URL are not allowed; pass them as a header")
} Type guard
func hasNoUserinfo(raw string) bool {
u, err := url.Parse(raw)
return err == nil && u.User == nil
} Try / catch
if err := ssrf.ValidateURL(ctx, raw, cfg); err != nil {
if strings.Contains(err.Error(), "credentials embedded") {
// strip userinfo and move to Authorization header, then retry
}
} Prevention
- Normalize stored URLs: parse, set u.User = nil, re-serialize before persisting.
- Teach users header-based auth (Bearer/Basic) in integration docs.
When it happens
Trigger: Calling ssrf.ValidateURL with any URL whose authority section contains 'user:pass@' or 'user@' before the host.
Common situations: Users pasting an API-key-in-URL from a provider dashboard (e.g. https://KEY@api.example.com/v1); basic-auth URLs copied from curl examples; internal endpoints documented with embedded basic auth.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ssrf: scheme %q not permitted (managed mode requires https)
- cave_sandbox_credential_capability_ambiguous
- invalid agent evidence identity
- invalid native session identity
- invalid Claude organization id
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/572892170ea8f795.
Report an issue: GitHub.