{"record":{"id":"20b84aab27771c0d","repo":"Kareadita/Kavita","slug":"url-unable-to-resolve","errorCode":null,"errorMessage":"url-unable-to-resolve","messagePattern":"url-unable-to-resolve","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"warning","filePath":"Kavita.Services/UrlValidationService.cs","lineNumber":32,"sourceCode":"    {\n        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))\n        {\n            throw new KavitaException(await localizationService.TranslateAsync(\"url-malformed\"));\n        }\n\n        if (!string.Equals(uri.Scheme, \"https\", StringComparison.OrdinalIgnoreCase))\n        {\n            throw new KavitaException(await localizationService.TranslateAsync(\"url-https-only\"));\n        }\n\n        IPAddress[] addresses;\n        try\n        {\n            addresses = await Dns.GetHostAddressesAsync(uri.Host);\n        }\n        catch (SocketException)\n        {\n            throw new KavitaException(await localizationService.TranslateAsync(\"url-unable-to-resolve\"));\n        }\n\n        if (addresses.Length == 0)\n        {\n            throw new KavitaException(await localizationService.TranslateAsync(\"url-unable-to-resolve\"));\n        }\n\n        foreach (var address in addresses)\n        {\n            if (IpBlocklist.IsBlockedAddress(address))\n            {\n                throw new KavitaException(await localizationService.TranslateAsync(\"url-blocked-address\"));\n            }\n        }\n    }\n}\n","sourceCodeStart":14,"sourceCodeEnd":49,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/UrlValidationService.cs#L14-L49","documentation":"Thrown by UrlValidationService.ValidateUrlAsync when Dns.GetHostAddressesAsync(uri.Host) raises a SocketException — the hostname cannot be resolved. This is the DNS-resolution step of the SSRF gate, performed after the URI is parsed and the scheme is verified. It is a localized KavitaException surfaced as HTTP 500 (or 400 where the caller wraps it).","triggerScenarios":"ValidateUrlAsync receives a well-formed https URL whose host does not resolve (NXDOMAIN, transient DNS failure, or no network). Reachable from cover/favicon/font/CBL/upload callers.","commonSituations":"Typo in the hostname; the Kavita server's DNS cannot reach the public resolver (split-horizon DNS, container DNS misconfig); the host is an internal-only name not resolvable from the server; temporary DNS outage.","solutions":["Verify the hostname resolves from the Kavita server itself (`nslookup host` / `dig host` in the container).","Correct typos and ensure the host is publicly resolvable if Kavita runs in a restricted network.","Configure the container/host DNS (e.g., /etc/resolv.conf or Docker dns:) to reach a resolver that can answer for the target."],"exampleFix":"// before\nvalidate(url: string) { /* none */ }\n// after\nvalidate(url: string) {\n  const host = new URL(url).hostname;\n  // best-effort client hint; server DNS is authoritative\n  if (!host.includes('.')) return throwError(() => new Error('Hostname looks invalid'));\n  return of(true);\n}","handlingStrategy":"try-catch","validationCode":"// best-effort client check; the server's DNS is authoritative\nfunction looksResolvableHost(url: string): boolean {\n  try { const h = new URL(url).hostname; return h.includes('.') && !/\\s/.test(h); } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try { await svc.fetchFromUrl(url); } catch (e) { if (/resolve/i.test(e.message)) showUser('Hostname could not be resolved; check the address or server DNS'); else throw e; }","preventionTips":["Verify resolution from the Kavita host/container (nslookup/dig).","Correct hostname typos before submitting.","Ensure the container's DNS resolver can reach public or required private zones."],"tags":["url","ssrf","dns","network"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}