JuliusBrussee/caveman · error

ssrf: destination %s (for host %q) is a private address; add

Error message

ssrf: destination %s (for host %q) is a private address; add %s to the SSRF allowlist (CAVE_SSRF_ALLOWLIST) to permit it

What it means

checkAddr blocked an RFC1918 private destination in self-hosted mode because no allowlist entry matches. The message names CAVE_SSRF_ALLOWLIST so operators with legitimate internal endpoints can opt in explicitly.

Source

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

	}

	for _, p := range blockedPrefixes {
		if p.Contains(addr) {
			// These ranges (link-local/metadata, ULA outside the narrow local-TUN
			// exception, multicast, unspecified, documentation) are absolutely
			// blocked — no allowlist escape in any mode.
			return fmt.Errorf("ssrf: destination %s (for host %q) is in blocked range %s", addr, host, p)
		}
	}

	if inRFC1918(addr) {
		if cfg.ManagedMode {
			return fmt.Errorf("ssrf: destination %s (for host %q) is a private address blocked in managed mode", addr, host)
		}
		// In self-hosted mode, RFC1918 is blocked unless the original hostname
		// OR the resolved IP literal appears in the allowlist.
		if !isInAllowList(host, port, cfg.AllowList) && !isInAllowList(addr.String(), port, cfg.AllowList) {
			return fmt.Errorf("ssrf: destination %s (for host %q) is a private address; add %s to the SSRF allowlist (CAVE_SSRF_ALLOWLIST) to permit it", addr, host, allowListSuggestion(addr, port))
		}
	}
	return nil
}

// allowListSuggestion renders the allowlist entry to advise for a blocked
// destination. It must be an entry isInAllowList would actually accept at BOTH
// stages: ValidateHost pre-flights with an empty port, so JoinHostPort would
// emit a trailing-colon token like "127.0.0.1:" that matches only the
// port-less stage — an operator following that advice literally relaxes the
// pre-flight guard, is blocked again at dial time by a second message naming a
// different token, and leaves a stale weakening entry behind. The bare address
// form is the one entry that matches every stage.
func allowListSuggestion(addr netip.Addr, port string) string {
	if port == "" {
		return addr.String()
	}
	return net.JoinHostPort(addr.String(), port)

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Add the original hostname or its IP to CAVE_SSRF_ALLOWLIST if the internal endpoint is intentional
  2. Otherwise point the request at the public address of the service
  3. Verify the allowlist entry format matches host and port
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at shared/platform/ssrf/ssrf.go:316 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/cb276272fe327f79. Report an issue: GitHub.