amir20/dozzle · warning · errBlockedAddress

webhook target resolves to a blocked address range

Error message

webhook target resolves to a blocked address range

What it means

errBlockedAddress is returned when a webhook URL's DNS resolution points into a blocked IP range (loopback, link-local, 0.0.0.0/8, etc.). Dozzle refuses these to prevent SSRF against its own host services and cloud metadata endpoints (169.254.169.254), while deliberately allowing RFC1918 private LANs for self-hosted webhooks.

Solutions

  1. Change the webhook URL to point at a routable address (a real LAN IP in RFC1918 space is allowed, e.g. 192.168.x.x, 10.x.x.x, 172.16-31.x.x).
  2. If the target genuinely is a local service, expose it on a private/LAN interface and use that address instead of 127.0.0.1/localhost.
  3. Check DNS: dig <host> and confirm none of the resolved IPs are loopback/link-local/0.0.0.0/8.
  4. If it was a misconfigured or malicious destination, remove or correct the destination in the notification settings.

Example fix

// before
url: http://127.0.0.1:8123/api/webhook
// after
url: http://192.168.1.50:8123/api/webhook
Defensive patterns

Strategy: validation

Validate before calling

// resolve the webhook host before saving the destination
ips, err := net.LookupIP(host)
if err != nil || len(ips) == 0 { return fmt.Errorf("cannot resolve webhook host") }
for _, ip := range ips {
  if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
    return fmt.Errorf("webhook host %s resolves to blocked address %s", host, ip)
  }
}

Try / catch

resp, err := client.SendTest(dest)
if errors.Is(err, errBlockedAddress) {
  // surface 'destination resolves to a blocked address range' to the user
}

Prevention

When it happens

Trigger: safeDialContext inspects resolved IPs during dialing and isBlockedIP matches (webhook.go:132); SendTest propagates it. Any webhook send to a URL whose hostname resolves to loopback, link-local, or 0.0.0.0/8 triggers it; when every resolved IP is blocked, lastErr falls back to errBlockedAddress (webhook.go:142).

Common situations: Pointing the webhook at localhost/127.0.0.1 or the host's own metadata service (intentionally or via config mistake); a DNS name that resolves to 169.254.x.x; hostname like 'localhost' or 0.0.0.0 in the destination URL; DNS rebinding-ish setups where internal names resolve to loopback.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/9966f235f26c6674. Report an issue: GitHub.

Appendix: source

Thrown at internal/notification/dispatcher/webhook.go:28

	"net"
	"net/http"
	"net/url"
	"slices"
	"strings"
	"text/template"
	"time"

	"github.com/amir20/dozzle/types"
	"github.com/rs/zerolog/log"
)

// errBlockedAddress is returned when a webhook URL resolves to a blocked
// address range. Loopback and link-local addresses are refused to prevent SSRF
// against the Dozzle host's own services and cloud metadata endpoints
// (e.g. 169.254.169.254). RFC1918 private ranges are intentionally allowed —
// self-hosted webhooks (Home Assistant, internal Mattermost, etc.) commonly
// live on private LANs.
var errBlockedAddress = errors.New("webhook target resolves to a blocked address range")

// zeroNetV4 covers 0.0.0.0/8 — on Linux these route to the local host.
var zeroNetV4 = &net.IPNet{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(8, 32)}

func isBlockedIP(ip net.IP) bool {
	if isBlockedBaseIP(ip) {
		return true
	}
	// IPv6 transition mechanisms (6to4, NAT64, Teredo, IPv4-compatible) embed an
	// arbitrary IPv4 address that none of the checks above look at. Unwrap and
	// re-check the embedded address so 2002:7f00:1::1 is treated as 127.0.0.1.
	return slices.ContainsFunc(embeddedIPv4(ip), isBlockedBaseIP)
}

func isBlockedBaseIP(ip net.IP) bool {
	if ip.IsLoopback() ||
		ip.IsLinkLocalUnicast() ||
		ip.IsLinkLocalMulticast() ||

View on GitHub (pinned to d9463cbe21)