Freika/dawarich · warning · UrlValidatable::BlockedUrlError

Invalid URL scheme: %{scheme}

Error message

Invalid URL scheme: %{scheme}

What it means

Raised as BlockedUrlError by Imports/UrlValidatable#validate_integration_url! when the parsed URL's scheme is anything other than http or https. Typical offenders: 'ftp://', 'file://', 'webdav://', a bare 'host/path' that URI.parse gives a nil scheme for, or typos like 'http//host'. It fires before DNS resolution, so it is purely a lexical scheme check.

Source

Thrown at app/services/concerns/url_validatable.rb:64

    IPAddr.new('10.0.0.0/8'),      # RFC1918
    IPAddr.new('100.64.0.0/10'),   # CGNAT (Tailscale uses this)
    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)

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Fix the stored URL to start with http:// or https:// (e.g. 'https://immich.example.com').
  2. If the input is a bare host, have the UI prepend a default scheme or normalize client-side before submitting.
  3. If the scheme looks present but still fails, print URI.parse(url).scheme to see what Ruby actually extracted (nil, 'ftp', garbage).
  4. Add a client-side pattern check like \Ahttps?:// before allowing save.

Example fix

# before
url = 'immich.lan:2283'           # URI scheme is nil
validate_integration_url!(url)    # -> BlockedUrlError: Invalid URL scheme

# after
url = 'http://immich.lan:2283'
validate_integration_url!(url)     # scheme 'http' passes
Defensive patterns

Strategy: validation

Validate before calling

require 'uri'
uri = URI.parse(url.to_s)
uri.scheme.in?(%w[http https]) # gate before save

Type guard

def http_url?(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

When it happens

Trigger: User saves an integration URL (Immich, PhotoPrism, OwnTracks endpoint) typed as 'immich.example.com' without a scheme (scheme is nil), 'ftp://server/export', or with a malformed prefix like 'http//:'. Also API clients posting url: 'localhost:2283' without http://.

Common situations: Users pasting hostnames copied from docs that omit the protocol, mobile clients constructing URLs from a host field, typos when editing self-hosted integration settings, URLs with trailing spaces or unicode characters that break URI.parse's scheme detection.

Related errors


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/e4a4ba8edb4c2160. Report an issue: GitHub.