Kareadita/Kavita · error · KavitaException
url-blocked-address
Error message
url-blocked-address
What it means
Thrown by UrlValidationService.ValidateUrlAsync when any resolved IP for the host is on IpBlocklist.IsBlockedAddress — loopback, private (10/8, 172.16/12, 192.168/16), CGNAT, link-local (169.254/16, AWS metadata), multicast, reserved, documentation/test-net, and IPv6 equivalents. This is Kavita's core SSRF defense: a user-supplied URL may not pull content from internal/private addresses. It is a localized KavitaException surfaced as HTTP 500 (or 400 where the caller wraps it).
Source
Thrown at Kavita.Services/UrlValidationService.cs:44
try
{
addresses = await Dns.GetHostAddressesAsync(uri.Host);
}
catch (SocketException)
{
throw new KavitaException(await localizationService.TranslateAsync("url-unable-to-resolve"));
}
if (addresses.Length == 0)
{
throw new KavitaException(await localizationService.TranslateAsync("url-unable-to-resolve"));
}
foreach (var address in addresses)
{
if (IpBlocklist.IsBlockedAddress(address))
{
throw new KavitaException(await localizationService.TranslateAsync("url-blocked-address"));
}
}
}
}
View on GitHub (pinned to 9c3e540000)
Solutions
- Use a URL whose host resolves to a public IP; host the asset on a publicly addressable https endpoint.
- If the content lives on the LAN, proxy it through Kavita or a public reverse proxy rather than passing an internal URL.
- Do not attempt to disable the blocklist; it is a security control — instead expose the resource over a public address.
Example fix
// before const url = 'https://192.168.1.5/cover.png'; // blocked // after const url = 'https://cdn.example.com/cover.png'; // public IP only
Defensive patterns
Strategy: validation
Validate before calling
function isPublicHost(url: string): boolean {
try {
const h = new URL(url).hostname;
// reject literal private/loopback IPs and link-local metadata hostnames
if (/^(127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2[0-9]|3[01])\.)/.test(h)) return false;
if (h === 'localhost' || h === '0.0.0.0') return false;
return true;
} catch { return false; }
} Type guard
function isPublicUrlCandidate(s: unknown): s is string {
return typeof s === 'string' && isPublicHost(s);
} Try / catch
try { await svc.fetchFromUrl(url); } catch (e) { if (/blocked address/i.test(e.message)) showUser('That URL points to a private/blocked address; use a public https URL'); else throw e; } Prevention
- Only submit URLs whose host resolves to a public IP.
- Host internal assets behind a public https reverse proxy instead of passing LAN URLs.
- Never attempt to bypass IpBlocklist; it is a security control.
When it happens
Trigger: ValidateUrlAsync receives an https URL whose host resolves (or literally is) an internal/private IP: localhost / 127.0.0.1, 192.168.x.x, 10.x.x.x, 172.16-31.x.x, 169.254.169.254 (cloud metadata), or a hostname that DNS-rebinds to such an address. Reachable from cover/favicon/font/CBL/upload callers.
Common situations: User tries to pull a cover or CBL from an internal/LAN host; a DNS-rebinding payload points a public-looking name at 127.0.0.1; referencing the Kavita server's own internal address; attempting to reach the 169.254.169.254 metadata endpoint.
Related errors
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/dc7b90c4fa4f44f7.
Report an issue: GitHub.