Freika/dawarich · warning · UrlValidatable::BlockedUrlError
URL resolves to a blocked address
Error message
URL resolves to a blocked address
What it means
Raised as BlockedUrlError when the URL's host resolves (via Resolv.getaddress) to an IP inside a blocked range — SSRF protection. ALWAYS_BLOCKED_RANGES covers loopback, private, link-local, and similar (127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16, ::1, fc00::/7, plus benchmark 198.18/15); non-self-hosted instances additionally apply CLOUD_ONLY_BLOCKED_RANGES. The error means the server refused to be pointed at its own or its network's internal addresses.
Source
Thrown at app/services/concerns/url_validatable.rb:77
def validate_integration_url!(url)
return if url.blank?
uri = URI.parse(url)
unless %w[http https].include?(uri.scheme)
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_scheme', scheme: uri.scheme)
end
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.host_required') if uri.host.blank?
# Cloud refuses URLs that embed credentials. Self-hosters legitimately
# use http://user:pass@host — homelab Immich behind nginx basic-auth
# is a real config we don't want to break.
if uri.userinfo.present? && !DawarichSettings.self_hosted?
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.embedded_credentials')
end
ip = IPAddr.new(Resolv.getaddress(uri.host))
if blocked_ranges.any? { |range| range.include?(ip) }
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.blocked_address')
end
rescue URI::InvalidURIError
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_format')
rescue Resolv::ResolvError
raise BlockedUrlError, I18n.t('services.concerns.url_validatable.unresolvable_host', host: uri.host)
end
def blocked_ranges
if DawarichSettings.self_hosted?
ALWAYS_BLOCKED_RANGES
else
ALWAYS_BLOCKED_RANGES + CLOUD_ONLY_BLOCKED_RANGES
end
end
end
View on GitHub (pinned to 97fad417c5)
Solutions
- Use a URL whose host resolves to a publicly reachable IP (port-forward/Tunnel the service, e.g. via Tailscale Funnel/Cloudflare Tunnel) when on cloud.
- On your own deployment, verify DawarichSettings.self_hosted? returns true — that narrows blocking to ALWAYS_BLOCKED_RANGES only (note loopback/private stay blocked even then).
- If you truly need localhost integrations self-hosted, run the integration on a non-loopback, non-RFC1918 address or adjust the concern deliberately, accepting the SSRF exposure.
- Check what the host actually resolves to from the server: Resolv.getaddress(host) in a Rails console.
Example fix
# before (cloud) url = 'http://192.168.1.50:2283' # resolves to private range -> BlockedUrlError # after url = 'https://immich.yourdomain.com' # public IP via tunnel/port-forward
Defensive patterns
Strategy: validation
Validate before calling
ip = IPAddr.new(Resolv.getaddress(URI.parse(url).host)) rescue nil
blocked = [IPAddr.new('127.0.0.0/8'), IPAddr.new('10.0.0.0/8'), IPAddr.new('172.16.0.0/12'), IPAddr.new('192.168.0.0/16'), IPAddr.new('169.254.0.0/16'), IPAddr.new('::1/128'), IPAddr.new('fc00::/7')]
blocked.any? { |r| r.include?(ip) } # pre-flight SSRF check Type guard
def public_http_url?(s) u = URI.parse(s.to_s) return false unless %w[http https].include?(u.scheme) && u.host ip = IPAddr.new(Resolv.getaddress(u.host)) !ip.private? && !ip.loopback? rescue StandardError false end
Try / catch
begin
validate_integration_url!(url)
rescue BlockedUrlError => e
render json: { error: 'Endpoint must be publicly reachable' }, status: :unprocessable_entity
end Prevention
- On cloud, expose homelab services through a tunnel before linking them.
- On self-host, confirm the self_hosted setting is correct so the intended (narrower) rule set applies.
- Document for users that localhost/LAN URLs cannot work from a hosted server.
When it happens
Trigger: Entering 'http://localhost:2283' or 'http://192.168.1.50:2283' as an integration URL on the hosted cloud (host resolves to loopback/private space and cloud ranges are in force); DNS names that resolve to internal IPs, such as a homelab dyn-domain from inside the cluster; a hostname whose public DNS has an A record in 10.0.0.0/8.
Common situations: Cloud users trying to point the service at machines on their home network (impossible by design — the cloud server cannot reach them anyway), self-hosters on localhost who have self_hosted? misconfigured false, split-horizon DNS where the name is public but resolves privately.
Related errors
- Invalid URL scheme: %{scheme}
- URL must not embed credentials (user:pass@host)
- Could not resolve hostname: %{host}
- URL must include a host
- Invalid URL format
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/43b9b944164df45c.
Report an issue: GitHub.