JuliusBrussee/caveman · error
ssrf: scheme not permitted (managed mode requires https)
Error message
ssrf: scheme %q not permitted (managed mode requires https)
What it means
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.
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()
Example fix
// before cfg := ssrf.ManagedConfig() err := ssrf.ValidateURL(ctx, "http://api.example.com/v1", cfg) // after cfg := ssrf.ManagedConfig() 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.Scheme != "https" && !(u.Scheme == "http" && !managed)) {
return fmt.Errorf("endpoint must use https in managed mode: got %q scheme", schemeOf(raw))
} Type guard
func isHTTPSEndpoint(raw string) bool {
u, err := url.Parse(raw)
return err == nil && u.Scheme == "https"
} Try / catch
if err := ssrf.ValidateURL(ctx, endpoint, cfg); err != nil {
var schemeErr bool
if strings.Contains(err.Error(), "not permitted") && strings.Contains(err.Error(), "scheme") {
schemeErr = true // fail fast with config guidance
}
return fmt.Errorf("endpoint config invalid (schemeErr=%v): %w", schemeErr, err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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://.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- githubapp: request path escaped configured host
- ssrf: credentials embedded in URL are forbidden
- ssrf: host is blocked (loopback)
- awscreds: AWS_CONTAINER_CREDENTIALS_FULL_URI is not a valid…
- awscreds: AWS_EC2_METADATA_SERVICE_ENDPOINT is not a valid…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/cc09d98e2a9d5174.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/ssrf/ssrf.go:216
return checkAddr(addr, host, port, cfg)
}
if strings.EqualFold(host, "localhost") && !(!cfg.ManagedMode && isInAllowList(host, port, cfg.AllowList)) {
return fmt.Errorf("ssrf: host %q is blocked (loopback)", host)
}
return nil
}
// parseValidatedURL applies the URL-shape policy — scheme, embedded
// credentials, managed-mode port — and returns the host/port to check.
func parseValidatedURL(raw string, cfg Config) (host, port string, err error) {
u, parseErr := url.Parse(raw)
if parseErr != 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"
}View on GitHub (pinned to 3ee70a1026)