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

  1. Point the URL at a genuinely public address.
  2. If the internal target is legitimate and you own the code path, pass the allowPrivate option the dialer exposes instead of bypassing the package.
  3. Fix split-horizon DNS or /etc/hosts entries that make a public name resolve privately.
  4. 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

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


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