{"record":{"id":"6aed7b7be9b22ef2","repo":"ory/hydra","slug":"dns-lookup-timed-out","errorCode":null,"errorMessage":"DNS lookup timed out","messagePattern":"DNS lookup timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/ipx/ip_validator.go","lineNumber":67,"sourceCode":"\tif parsed, err := url.ParseRequestURI(ipOrHostnameOrURL); err == nil {\n\t\tipOrHostname = parsed.Hostname()\n\t}\n\n\tif ip, err := netip.ParseAddr(ipOrHostname); err == nil {\n\t\tif !allowed(ip) {\n\t\t\treturn errors.Errorf(\"ip %s is not a permitted destination\", ip)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif addr, err := netip.ParseAddrPort(ipOrHostnameOrURL); err == nil {\n\t\tif !allowed(addr.Addr()) {\n\t\t\treturn errors.Errorf(\"ip %s is not a permitted destination\", addr.Addr())\n\t\t}\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeoutCause(ctx, 2*time.Second, errors.New(\"DNS lookup timed out\"))\n\tdefer cancel()\n\tips, err := resolver.LookupNetIP(ctx, \"ip\", ipOrHostname)\n\tif err != nil {\n\t\tif dnsErr, ok := stderrors.AsType[*net.DNSError](err); ok {\n\t\t\t// Copy the `*net.DNSError` before masking `Server` to avoid a data\n\t\t\t// race: the DNS resolver uses `singleflight` to deduplicate\n\t\t\t// concurrent lookups, so multiple goroutines may receive the same\n\t\t\t// `*net.DNSError` pointer. Mutating it in place races with concurrent\n\t\t\t// readers (e.g. the `otelhttp` `dnsDone` trace hook).\n\t\t\tmaskedDNS := *dnsErr\n\t\t\tmaskedDNS.Server = \"\" // Mask our DNS server's IP address.\n\t\t\treturn errors.Wrapf(&maskedDNS, \"failed to resolve %s\", ipOrHostnameOrURL)\n\t\t}\n\t}\n\n\tfor _, ip := range ips {\n\t\tif !allowed(ip) {\n\t\t\treturn errors.Wrapf(&net.DNSError{","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/ipx/ip_validator.go#L49-L85","documentation":"ipx.IsAssociatedIPAllowed resolves hostnames before checking IP rules; it bounds DNS resolution with a 2-second context whose cause is errors.New(\"DNS lookup timed out\"). When resolution exceeds that deadline (or fails with a timeout-class DNS error), the context's cause surfaces this message so callers can distinguish slow/unreachable DNS from disallowed destinations.","triggerScenarios":"Calling IsAssociatedIPAllowed (directly or via IsAssociatedIPAllowedWhenSet, or SSRF-check helpers) with ipOrHostname set to a hostname whose DNS resolution does not complete within 2 seconds — e.g. an internal name only resolvable by a slow corporate resolver, or a resolver outage.","commonSituations":"SSRF-checking a webhook URL whose host is an internal .local/cluster-internal name that public resolvers cannot answer; DNS infrastructure degradation (resolver timeout, ndots search-domain churn in k8s); IPv6-only hostnames with broken AAAA paths.","solutions":["Retry the operation — the failure may be transient resolver latency; the 2s bound is fixed, so retrying is the primary mitigation.","Resolve hostnames with your own resolver (with a larger deadline) and pass the resolved IP instead of the hostname, skipping the lookup path.","Ensure DNS infrastructure is healthy: correct /etc/resolv.conf, reachable upstream resolvers, sane ndots/search config in containers.","Allowlist known internal hostnames as IPs ahead of time so the lookup path is not exercised at request time."],"exampleFix":"// before\nallowed, err := ipx.IsAssociatedIPAllowedWhenSet(ctx, \"internal.svc.cluster.local\", set)\n// after\nips, err := myResolver.LookupHost(ctx2, \"internal.svc.cluster.local\") // own, longer deadline\nallowed, err = ipx.IsAssociatedIPAllowedWhenSet(ctx, ips[0], set)","handlingStrategy":"retry","validationCode":"// pre-resolve with your own deadline if DNS is slow:\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\nips, err := net.DefaultResolver.LookupNetIP(ctx, \"ip\", hostname)\nif err != nil { return fmt.Errorf(\"unresolvable host %q: %w\", hostname, err) }\n// then call IsAssociatedIPAllowed with ips[0].String()","typeGuard":"func isDNSTimeout(err error) bool {\n  var dnsErr *net.DNSError\n  return errors.As(err, &dnsErr) && dnsErr.IsTimeout\n}","tryCatchPattern":"allowed, err := ipx.IsAssociatedIPAllowedWhenSet(ctx, host, set)\nif err != nil {\n  if strings.Contains(err.Error(), \"DNS lookup timed out\") || isDNSTimeout(err) {\n    return retry.WithBackoff(func() error {\n      var e error\n      allowed, e = ipx.IsAssociatedIPAllowedWhenSet(ctx, host, set)\n      return e\n    })\n  }\n  return err\n}","preventionTips":["Retry once or twice — the 2s bound makes transient slowness common","Fix resolver config (resolv.conf, ndots, search domains) in containers","Pass pre-resolved IPs for known internal hostnames","Monitor resolver latency; alert when p99 nears 2s"],"tags":["dns","network","timeout","ssrf"],"backgroundTag":"dns-lookup-timeout","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}