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 it to the allowlist to permit it

What it means

checkAddr in ssrf: the address is RFC1918 private and mode is self-hosted (ManagedMode false). Private ranges are blocked by default there too, but the operator can opt in per-target: the original hostname OR the resolved IP literal must appear in cfg.AllowList for that port.

Source

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

	}

	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 it to the allowlist to permit it", addr, host)
		}
	}
	return nil
}

func inRFC1918(addr netip.Addr) bool {
	for _, p := range privateRFC1918 {
		if p.Contains(addr) {
			return true
		}
	}
	return false
}

func isInAllowList(host, port string, list []string) bool {
	h := strings.ToLower(strings.Trim(host, "[]"))
	for _, entry := range list {
		raw := strings.TrimSpace(entry)

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Add the exact 'host:port' or 'ip:port' to cfg.AllowList (e.g. '192.168.1.20:9200').
  2. Confirm the port in the allowlist entry matches the dialed port exactly — entries are port-specific.
  3. Keep entries minimal: allowlist the specific service, not the whole /8 by hostname wildcard.

Example fix

// before
cfg := ssrf.Config{ManagedMode: false}
ssrf.ValidateURL(ctx, "https://192.168.1.20:9200/index", cfg) // blocked

// after
cfg := ssrf.Config{ManagedMode: false, AllowList: []string{"192.168.1.20:9200"}}
ssrf.ValidateURL(ctx, "https://192.168.1.20:9200/index", cfg)
Defensive patterns

Strategy: validation

Validate before calling

target := net.JoinHostPort(host, port)
ipTarget := net.JoinHostPort(ipLiteral, port)
if !contains(cfg.AllowList, target) && !contains(cfg.AllowList, ipTarget) {
    return fmt.Errorf("private target %s must be allowlisted as 'host:port' or 'ip:port'", target)
}

Try / catch

if err := ssrf.ValidateURL(ctx, raw, cfg); err != nil {
    if !cfg.ManagedMode && strings.Contains(err.Error(), "add it to the allowlist") {
        // guide the operator to add host:port to AllowList, then re-validate
    }
}

Prevention

When it happens

Trigger: Validating/dialing 10.x/172.16-31.x/192.168.x in self-hosted mode when neither the hostname nor the IP literal has a matching AllowList entry (host:port or ip:port).

Common situations: Self-hosted install pointing integrations at an internal NAS, database host, or cluster service (e.g. 192.168.1.20:9200) without allowlisting; hostname allowlisted but on a different port than dialed.

Related errors


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