{"record":{"id":"3a3e8a3b54d35b85","repo":"MHSanaei/3x-ui","slug":"host-s-resolves-to-blocked-private-internal-addre","errorCode":null,"errorMessage":"host %s resolves to blocked private/internal address %s","messagePattern":"host (.+?) resolves to blocked private/internal address (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/web/service/url_safety.go","lineNumber":80,"sourceCode":"}\n\nfunc rejectPrivateHost(ctx context.Context, hostname string) error {\n\tif ip := net.ParseIP(hostname); ip != nil {\n\t\tif isBlockedIP(ip) {\n\t\t\treturn fmt.Errorf(\"blocked private/internal address %s\", ip.String())\n\t\t}\n\t\treturn nil\n\t}\n\tips, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot resolve host %s: %w\", hostname, err)\n\t}\n\tif len(ips) == 0 {\n\t\treturn fmt.Errorf(\"host %s has no IP addresses\", hostname)\n\t}\n\tfor _, ipAddr := range ips {\n\t\tif isBlockedIP(ipAddr.IP) {\n\t\t\treturn fmt.Errorf(\"host %s resolves to blocked private/internal address %s\", hostname, ipAddr.IP.String())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc isBlockedIP(ip net.IP) bool {\n\treturn netsafe.IsBlockedIP(ip)\n}\n","sourceCodeStart":62,"sourceCodeEnd":89,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/url_safety.go#L62-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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."],"exampleFix":"// before\nu := \"http://10-0-0-5.sslip.io/api\" // wildcard DNS resolving to 10.0.0.5\n\n// after\nu := \"https://api.example.com/\" // real public name","handlingStrategy":"validation","validationCode":"// Optional pre-flight: resolve and verify all addresses are public before storing the URL\nips, _ := net.LookupHost(hostname)\nfor _, ip := range ips {\n    if parsed := net.ParseIP(ip); parsed != nil && netsafe.IsBlockedIP(parsed) {\n        return fmt.Errorf(\"%q resolves to private address %s\", hostname, ip)\n    }\n}","typeGuard":"null","tryCatchPattern":"if strings.Contains(err.Error(), \"resolves to blocked\") {\n    // rebinding or split-horizon DNS: choose a fully public target; never whitelist\n}","preventionTips":["Ban *.nip.io / *.sslip.io style names from outbound URL configs by policy.","Prefer stable public DNS names for all externally-fetched endpoints."],"tags":["ssrf","dns-rebinding","security","network"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}