Freika/dawarich · warning · UrlValidatable::BlockedUrlError
URL must include a host
Error message
URL must include a host
What it means
Raised as BlockedUrlError by validate_integration_url! when the URI parses and has an acceptable http/https scheme but uri.host is blank. This happens for URLs whose authority component is missing: 'https:///path', 'https://:8080/x', or scheme-only strings like 'https://' — the parser accepts them but there is no host to resolve or connect to.
Source
Thrown at app/services/concerns/url_validatable.rb:66
IPAddr.new('127.0.0.0/8'), # IPv4 loopback
IPAddr.new('172.16.0.0/12'), # RFC1918
IPAddr.new('192.0.0.0/24'), # IETF protocol assignments
IPAddr.new('192.168.0.0/16'), # RFC1918
IPAddr.new('198.18.0.0/15'), # benchmark
IPAddr.new('::1/128'), # IPv6 loopback
IPAddr.new('fc00::/7') # IPv6 ULA
].freeze
private
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
View on GitHub (pinned to 97fad417c5)
Solutions
- Re-enter the URL with a real hostname: 'https://immich.example.com:2283'.
- If the URL is assembled from parts, guard each part is non-empty before building, or fail the save in the form.
- Inspect URI.parse(url) in a console (scheme/host/port/path) to see where the host went.
- Make the host field required and validated client-side.
Example fix
# before
base = ENV['IMMICH_HOST'] # nil in this env
url = "https://#{base}:2283" # => "https://:2283", host nil
# after
base = ENV.fetch('IMMICH_HOST') { raise 'IMMICH_HOST not set' }
url = "https://#{base}:2283" # host present Defensive patterns
Strategy: validation
Validate before calling
uri = URI.parse(url.to_s) uri.host.present? || errors.add(:url, 'host missing')
Type guard
def url_with_host?(s) u = URI.parse(s.to_s) %w[http https].include?(u.scheme) && u.host.present? rescue URI::InvalidURIError false end
Try / catch
begin validate_integration_url!(url) rescue BlockedUrlError => e errors.add(:url, e.message) end
Prevention
- Build URLs from validated parts; never interpolate possibly-empty variables into the authority.
- Require the host field in integration forms.
- Test URL templates with missing parts in your suite.
When it happens
Trigger: Saving 'https://' plus only a path or port ('https:///api', 'https://:2283'), a URL built by string interpolation where the host variable was empty ("https://#{host}:2283" with host=''), or user input that is just the scheme.
Common situations: Settings forms where the host field is optional and left blank while the client auto-prepends https://, template/env interpolation producing empty hosts, copy-paste that drops the hostname, trailing-punctuation hosts that URI.parse pushes into the path.
Related errors
- Invalid URL scheme: %{scheme}
- Invalid URL format
- URL must not embed credentials (user:pass@host)
- URL resolves to a blocked address
- Could not resolve hostname: %{host}
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/e7aa718410304bf1.
Report an issue: GitHub.