MHSanaei/3x-ui · warning

blocked private/internal address %s

Error message

blocked private/internal address %s

What it means

Thrown by rejectPrivateHost (reached via SanitizePublicHTTPURL, the variant that enforces SSRF rules at request time) when the URL's hostname is a literal IP that falls in a blocked range. netsafe.IsBlockedIP blocks loopback, RFC1918/private, link-local, multicast, unspecified, and typically IPv6 ULA/link-local ranges, so the panel never makes outbound requests into its own network stack.

Source

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

	if allowPrivate {
		return clean, nil
	}
	u, err := url.Parse(clean)
	if err != nil {
		return "", err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := rejectPrivateHost(ctx, u.Hostname()); err != nil {
		return "", err
	}
	return clean, nil
}

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
}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Point the URL at a genuinely public host; if the target is an internal service, do not route it through the SSRF-checked outbound path — expose it via a purpose-built config (e.g. a local inbound) instead.
  2. If the destination is public but accessed via a private NAT address, use its public DNS name (note: the DNS-resolved form is also checked, see the 'resolves to blocked' sibling error).
  3. Never try to bypass with decimal/octal IP encodings — url parsing normalizes most, and the resolve-time check catches the rest; treat a genuine need for private targets as a design change, not a workaround.

Example fix

// before
u := "http://127.0.0.1:9090/health" // health-check to a local sidecar

// after — use the service's public name, or move the check off the SSRF-guarded path
u := "https://sidecar.example.com/health"
Defensive patterns

Strategy: validation

Validate before calling

// Reject private IP literals before they ever reach the outbound path
func isSafeOutboundHost(host string) bool {
    if ip := net.ParseIP(host); ip != nil {
        return !netsafe.IsBlockedIP(ip)
    }
    return true // names get the resolve-time check
}

Type guard

null

Try / catch

if _, err := service.SanitizePublicHTTPURL(u); err != nil {
    if strings.Contains(err.Error(), "blocked private/internal address") {
        // configuration error: switch to a public target; never retry as-is
    }
}

Prevention

When it happens

Trigger: Passing 'http://127.0.0.1:port/...', 'http://10.0.0.5/...', 'http://192.168.1.1/', 'http://[::1]/...', or 'http://169.254.169.254/' (cloud metadata) as an outbound URL to any feature that routes through SanitizePublicHTTPURL.

Common situations: Admins pointing a health-check/URL-test at a local reverse proxy or another service on the same box; attempts (accidental or malicious) to reach Docker's 172.17.x.x bridge or the cloud metadata IP; localhost shortcuts copied from dev configs into production panel settings.

Related errors


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