{"record":{"id":"49f88c1f530cc1b5","repo":"Tencent/WeKnora","slug":"dns-resolution-failed-for-s-w-49f88c","errorCode":null,"errorMessage":"DNS resolution failed for %s: %w","messagePattern":"DNS resolution failed for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/security.go","lineNumber":832,"sourceCode":"\thostLower := strings.ToLower(host)\n\tfor _, restricted := range restrictedHostnames {\n\t\tif hostLower == restricted {\n\t\t\treturn nil, fmt.Errorf(\"connection blocked: hostname %s is restricted\", host)\n\t\t}\n\t}\n\tfor _, suffix := range restrictedHostSuffixes {\n\t\tif strings.HasSuffix(hostLower, suffix) {\n\t\t\treturn nil, fmt.Errorf(\"connection blocked: hostname suffix %s is restricted\", suffix)\n\t\t}\n\t}\n\n\t// Resolve the hostname once, validate every answer, and then dial one of\n\t// those exact IPs. Dialing the original hostname here would make the\n\t// standard dialer resolve it a second time, leaving a DNS-rebinding window\n\t// between validation and connection establishment.\n\tips, err := net.DefaultResolver.LookupIPAddr(ctx, host)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"DNS resolution failed for %s: %w\", host, err)\n\t}\n\tif len(ips) == 0 {\n\t\treturn nil, fmt.Errorf(\"DNS resolution returned no addresses for %s\", host)\n\t}\n\n\t// Validate all resolved IPs\n\tfor _, ipAddr := range ips {\n\t\tif restricted, reason := isRestrictedIP(ipAddr.IP); restricted {\n\t\t\treturn nil, fmt.Errorf(\"connection blocked: %s resolves to restricted IP %s (%s)\", host, ipAddr.IP.String(), reason)\n\t\t}\n\t}\n\n\t// If we get here, all IPs are safe. Pin the connection to the validated DNS\n\t// answers; TLS still uses the request hostname for SNI/certificate checks.\n\tdialer := &net.Dialer{\n\t\tTimeout:   30 * time.Second,\n\t\tKeepAlive: 30 * time.Second,\n\t}","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/security.go#L814-L850","documentation":"SSRFSafeDialContext resolves the hostname itself (once, via net.DefaultResolver.LookupIPAddr) so it can validate every answer before connecting, and this error means that lookup failed. The library resolves explicitly to close the DNS-rebinding window between validation and connection; a resolver failure aborts the dial. The wrapped error (%w) carries the underlying DNS failure (NXDOMAIN, timeout, no such host, resolver unreachable).","triggerScenarios":"Dialing a hostname that does not exist in DNS, with an unreachable/misconfigured resolver, under a network policy that blocks UDP/TCP 53, or when the resolver times out before LookupIPAddr returns — reached via SSRFSafeDialContext, SSRFSafeGRPCDialer, or an http.Transport using it as DialContext.","commonSituations":"Typo in the hostname in config; running in a container/air-gapped network without working DNS; DNS outage or flaky resolvers; corporate networks that require an internal resolver not visible to the process.","solutions":["Verify the hostname resolves from the same environment: run a lookup (nslookup/getent hosts or net.LookupIP in Go) and fix the name or DNS records if it fails there too.","Fix resolver configuration (/etc/resolv.conf, VPC DNS settings, CoreDNS) so the process can reach a working nameserver.","Retry with backoff if the wrapped error indicates a transient resolver timeout.","If the host is IP-only anyway, dial the literal IP (still subject to isRestrictedIP checks) to skip DNS entirely."],"exampleFix":"// before\nconn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", \"api.exmaple.com:443\") // typo, NXDOMAIN\n\n// after\nif _, err := net.LookupIP(\"api.example.com\"); err != nil { return fmt.Errorf(\"resolve check failed: %w\", err) }\nconn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", \"api.example.com:443\")","handlingStrategy":"retry","validationCode":"ips, err := net.LookupIP(host)\nif err != nil {\n    return fmt.Errorf(\"pre-flight DNS check failed for %s: %w\", host, err)\n}","typeGuard":null,"tryCatchPattern":"conn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", addr)\nif err != nil && strings.Contains(err.Error(), \"DNS resolution failed\") {\n    var dnsErr *net.DNSError\n    if errors.As(err, &dnsErr) && dnsErr.IsTimeout {\n        // transient: retry with backoff\n    }\n    return nil, fmt.Errorf(\"check DNS/resolver configuration for %s: %w\", host, err)\n}","preventionTips":["Verify /etc/resolv.conf and VPC DNS settings in every environment (containers, CI, prod).","Spell-check hostnames against DNS records during config review.","Add retry-with-backoff for transient DNSError timeouts.","Monitor resolver health; alert on rising DNS failure rates."],"tags":["dns","resolution-failed","network","dial"],"backgroundTag":"dns-resolution-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}