MHSanaei/3x-ui · error
blocked private/internal address %s
Error message
blocked private/internal address %s
What it means
The SSRF-guarding dialer in internal/util/netsafe resolves the host and refuses to connect when a resolved IP is private/internal (loopback, RFC1918, link-local, etc.) and allowPrivate is false. The offending IP is reported in the message. The loop 'continue's, so the error is only returned if no other resolved address connects; it exists to stop server-side request forgery toward internal networks.
Source
Thrown at internal/util/netsafe/netsafe.go:48
func SSRFGuardedDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
allowPrivate := AllowPrivateFromContext(ctx)
var ips []net.IPAddr
if ip := net.ParseIP(host); ip != nil {
ips = []net.IPAddr{{IP: ip}}
} else {
ips, err = net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
}
var lastErr error
for _, ipAddr := range ips {
if !allowPrivate && IsBlockedIP(ipAddr.IP) {
lastErr = fmt.Errorf("blocked private/internal address %s", ipAddr.IP)
continue
}
conn, derr := defaultDialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))
if derr == nil {
return conn, nil
}
lastErr = derr
}
if lastErr == nil {
lastErr = fmt.Errorf("no usable address for %s", host)
}
return nil, lastErr
}
var hostnamePattern = regexp.MustCompile(`^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$`)
func NormalizeHost(addr string) (string, error) {
addr = strings.TrimSpace(addr)View on GitHub (pinned to ad32144c42)
Solutions
- Point the URL at a genuinely public address.
- If the internal target is legitimate and you own the code path, pass the allowPrivate option the dialer exposes instead of bypassing the package.
- Fix split-horizon DNS or /etc/hosts entries that make a public name resolve privately.
- Never use this guard's dialer for admin-configured loopback proxies — the codebase routes those through netproxy.NewHTTPClient instead.
Example fix
// before
resp, err := safeGet("http://localhost:8080/sub") // blocked private/internal address 127.0.0.1
// after
resp, err := safeGetAllowPrivate("http://127.0.0.1:8080/sub") // only if target is truly intended Defensive patterns
Strategy: try-catch
Validate before calling
ips, err := net.LookupIP(host)
if err != nil { return err }
for _, ip := range ips {
if netsafe.IsBlockedIP(ip) {
return fmt.Errorf("refusing internal target %s; configure a public address", ip)
}
} Try / catch
conn, err := safeDial(ctx, network, addr)
if err != nil && strings.Contains(err.Error(), "blocked private/internal") {
// target resolved internally: fix the URL or use an API that permits private targets
return fmt.Errorf("SSRF guard rejected %s: %w", addr, err)
} Prevention
- Treat 'blocked private/internal' as a configuration smell, never catch-and-bypass it.
- Keep admin-configured loopback proxies on netproxy.NewHTTPClient, which is exempt by design.
- Beware DNS rebinding: validate again at connect time (the dialer already does).
When it happens
Trigger: Calling the safe dialer / any higher-level fetch that uses it (subscription fetch, URL preview, geo update) with a hostname resolving only to private IPs (e.g. 'localhost', 'db.internal', '10.0.0.5') while allowPrivate=false; or directly with a literal private IP like 127.0.0.1 or 169.254.169.254.
Common situations: Testing a subscription URL that points at localhost; a DNS record (split-horizon or rebind) resolving a public-looking name to an internal IP; trying to reach the panel's own LAN address through a feature that enforces the guard; accidentally using a service-internal hostname.
Related errors
- blocked private/internal address %s
- host %s resolves to blocked private/internal address %s
- no usable address for %s
- stopped after 10 redirects
- cannot resolve host %s: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/adac7924954cd6e3.
Report an issue: GitHub.