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
- Add the exact 'host:port' or 'ip:port' to cfg.AllowList (e.g. '192.168.1.20:9200').
- Confirm the port in the allowlist entry matches the dialed port exactly — entries are port-specific.
- 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
- Allowlist entries are port-specific — match the exact port being dialed.
- Prefer allowlisting a stable internal hostname over a re-assignable pod IP.
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
- ssrf: destination %s (for host %q) is a private address bloc
- ssrf: scheme %q not permitted (managed mode requires https)
- ssrf: host %q is blocked (loopback)
- ssrf: DNS resolution failed for %q: %w
- ssrf: host %q resolved to no addresses
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/91911a0f07c3e87b.
Report an issue: GitHub.