MHSanaei/3x-ui · warning

host %s resolves to blocked private/internal address %s

Error message

host %s resolves to blocked private/internal address %s

What it means

The DNS-rebinding half of the SSRF guard: the hostname itself is fine, but at least one of its resolved IPs is in a blocked private/internal range (via netsafe.IsBlockedIP). Unlike the IP-literal check, this fires even when the user typed an innocent-looking domain — it defeats rebinding attacks where a public DNS name flips between a public IP (to pass earlier checks) and 127.0.0.1/169.254.169.254 at request time.

Source

Thrown at internal/web/service/url_safety.go:80

}

func rejectPrivateHost(ctx context.Context, hostname string) error {
	if ip := net.ParseIP(hostname); ip != nil {
		if isBlockedIP(ip) {
			return fmt.Errorf("blocked private/internal address %s", ip.String())
		}
		return nil
	}
	ips, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)
	if err != nil {
		return fmt.Errorf("cannot resolve host %s: %w", hostname, err)
	}
	if len(ips) == 0 {
		return fmt.Errorf("host %s has no IP addresses", hostname)
	}
	for _, ipAddr := range ips {
		if isBlockedIP(ipAddr.IP) {
			return fmt.Errorf("host %s resolves to blocked private/internal address %s", hostname, ipAddr.IP.String())
		}
	}
	return nil
}

func isBlockedIP(ip net.IP) bool {
	return netsafe.IsBlockedIP(ip)
}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Point at the true public address of the target (its public DNS name whose records are all public).
  2. If split-horizon DNS is making an internal name resolve privately, use the externally-resolvable variant or redesign: this outbound path is deliberately closed to internal targets.
  3. Do not attempt to pin the host in /etc/hosts to a public IP — the check resolves fresh each request and this path is intended to be strict.

Example fix

// before
u := "http://10-0-0-5.sslip.io/api" // wildcard DNS resolving to 10.0.0.5

// after
u := "https://api.example.com/" // real public name
Defensive patterns

Strategy: validation

Validate before calling

// Optional pre-flight: resolve and verify all addresses are public before storing the URL
ips, _ := net.LookupHost(hostname)
for _, ip := range ips {
    if parsed := net.ParseIP(ip); parsed != nil && netsafe.IsBlockedIP(parsed) {
        return fmt.Errorf("%q resolves to private address %s", hostname, ip)
    }
}

Type guard

null

Try / catch

if strings.Contains(err.Error(), "resolves to blocked") {
    // rebinding or split-horizon DNS: choose a fully public target; never whitelist
}

Prevention

When it happens

Trigger: A configured URL whose DNS has an A record pointing at 10.x/127.x/169.254.169.254/[::1]/fc00::/7 — e.g. a split-horizon name that resolves privately on the panel's network, a wildcard DNS service (sslip.io/nip.io style like 10-0-0-5.sslip.io), or an actually malicious rebinding setup.

Common situations: Using *.nip.io / *.sslip.io shortcuts to address internal services (they intentionally resolve to private IPs and are blocked by design); corporate split-DNS where 'app.corp' resolves to 10.x from inside; local dev hostnames like 'localhost.direct'.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/3a3e8a3b54d35b85. Report an issue: GitHub.