{"record":{"id":"adac7924954cd6e3","repo":"MHSanaei/3x-ui","slug":"blocked-private-internal-address-s","errorCode":null,"errorMessage":"blocked private/internal address %s","messagePattern":"blocked private/internal address (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/util/netsafe/netsafe.go","lineNumber":48,"sourceCode":"func SSRFGuardedDialContext(ctx context.Context, network, addr string) (net.Conn, error) {\n\thost, port, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tallowPrivate := AllowPrivateFromContext(ctx)\n\tvar ips []net.IPAddr\n\tif ip := net.ParseIP(host); ip != nil {\n\t\tips = []net.IPAddr{{IP: ip}}\n\t} else {\n\t\tips, err = net.DefaultResolver.LookupIPAddr(ctx, host)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tvar lastErr error\n\tfor _, ipAddr := range ips {\n\t\tif !allowPrivate && IsBlockedIP(ipAddr.IP) {\n\t\t\tlastErr = fmt.Errorf(\"blocked private/internal address %s\", ipAddr.IP)\n\t\t\tcontinue\n\t\t}\n\t\tconn, derr := defaultDialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))\n\t\tif derr == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\tlastErr = derr\n\t}\n\tif lastErr == nil {\n\t\tlastErr = fmt.Errorf(\"no usable address for %s\", host)\n\t}\n\treturn nil, lastErr\n}\n\nvar 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])?)*$`)\n\nfunc NormalizeHost(addr string) (string, error) {\n\taddr = strings.TrimSpace(addr)","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/util/netsafe/netsafe.go#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nresp, err := safeGet(\"http://localhost:8080/sub\") // blocked private/internal address 127.0.0.1\n\n// after\nresp, err := safeGetAllowPrivate(\"http://127.0.0.1:8080/sub\") // only if target is truly intended","handlingStrategy":"try-catch","validationCode":"ips, err := net.LookupIP(host)\nif err != nil { return err }\nfor _, ip := range ips {\n    if netsafe.IsBlockedIP(ip) {\n        return fmt.Errorf(\"refusing internal target %s; configure a public address\", ip)\n    }\n}","typeGuard":null,"tryCatchPattern":"conn, err := safeDial(ctx, network, addr)\nif err != nil && strings.Contains(err.Error(), \"blocked private/internal\") {\n    // target resolved internally: fix the URL or use an API that permits private targets\n    return fmt.Errorf(\"SSRF guard rejected %s: %w\", addr, err)\n}","preventionTips":["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)."],"tags":["security","ssrf","network","dialer"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}