JuliusBrussee/caveman · error

ssrf: URL must contain a host

Error message

ssrf: URL must contain a host

What it means

ssrf.ValidateURL requires a non-empty host component (u.Hostname()). URLs like 'https:///path', 'https://?q=1', or scheme-only strings parse successfully but have no host, so the guard rejects them before DNS resolution.

Source

Thrown at shared/platform/ssrf/ssrf.go:174

//
// 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)
}

// ValidateHost resolves host (bare hostname or IP literal) and checks all
// resolved addresses.  Use when you have a host/port pair rather than a full
// URL.

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Check the host/env var is set before constructing the URL.
  2. Require a full absolute URL at the input layer (form field placeholder/validation) and reject relative paths.
  3. Default the host explicitly (e.g. api.example.com) instead of concatenating possibly-empty values.

Example fix

// before
u := "https://" + os.Getenv("PROVIDER_HOST") + "/v1" // host unset -> https:///v1

// after
host := os.Getenv("PROVIDER_HOST")
if host == "" { return errors.New("PROVIDER_HOST not set") }
u := "https://" + host + "/v1"
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(raw)
if err != nil || u.Hostname() == "" {
    return fmt.Errorf("a complete absolute URL with host is required")
}

Type guard

func hasHost(raw string) bool {
    u, err := url.Parse(raw)
    return err == nil && u.Hostname() != ""
}

Prevention

When it happens

Trigger: Calling ssrf.ValidateURL with a URL whose authority is empty: 'https:///health', 'https:/health', relative paths like '/api/hook', or empty strings that still parse.

Common situations: Building URLs by string concatenation where the host variable is empty (unset env var); users entering only a path in a webhook field; a config default of '' slipping through.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/ebb56e376edace00. Report an issue: GitHub.