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
- Point at the true public address of the target (its public DNS name whose records are all public).
- 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.
- 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
- Ban *.nip.io / *.sslip.io style names from outbound URL configs by policy.
- Prefer stable public DNS names for all externally-fetched endpoints.
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
- blocked private/internal address %s
- blocked private/internal address %s
- stopped after 10 redirects
- cannot resolve host %s: %w
- host %s has no IP addresses
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/3a3e8a3b54d35b85.
Report an issue: GitHub.