Freika/dawarich · warning · UrlValidatable::BlockedUrlError

URL must not embed credentials (user:pass@host)

Error message

URL must not embed credentials (user:pass@host)

What it means

Raised as BlockedUrlError when the URL embeds userinfo ('user:pass@host') and the instance is NOT self-hosted (DawarichSettings.self_hosted? is false, i.e. the cloud deployment). Basic-auth-in-URL credentials are a leakage risk (they end up in logs, referrers, error reports), so cloud refuses them; the source comment notes self-hosters legitimately use them for e.g. Immich behind nginx basic auth, which is why the check is environment-gated.

Source

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

    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

  def blocked_ranges
    if DawarichSettings.self_hosted?
      ALWAYS_BLOCKED_RANGES
    else
      ALWAYS_BLOCKED_RANGES + CLOUD_ONLY_BLOCKED_RANGES
    end

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Remove the credentials from the URL and configure auth the supported way (API key field / header-based auth for the integration).
  2. If you are running your own instance, confirm self_hosted? is true in DawarichSettings — then this check is skipped by design.
  3. Never paste user:pass URLs into cloud settings; rotate those credentials if you already did.

Example fix

# before (cloud instance)
url = 'https://alice:hunter2@immich.example.com' # -> BlockedUrlError

# after
url = 'https://immich.example.com'
# supply auth via the integration's API key configuration instead
Defensive patterns

Strategy: validation

Validate before calling

uri = URI.parse(url.to_s)
uri.userinfo.nil? || DawarichSettings.self_hosted? # warn cloud users pre-save

Type guard

def credential_free_url?(s)
  URI.parse(s.to_s).userinfo.nil?
rescue URI::InvalidURIError
  false
end

Try / catch

begin
  validate_integration_url!(url)
rescue BlockedUrlError => e
  # strip user:pass@ and configure API-key auth instead
  url = url.sub(%r{\A([a-z]+://)[^@/]+@}i, '\1')
  retry
end

Prevention

When it happens

Trigger: On the hosted/cloud deployment, saving an integration URL like 'https://alice:secret@immich.example.com'. The same URL works on a self-hosted instance. Also triggered when a user pastes a browser URL that still contains basic-auth credentials.

Common situations: Users migrating a self-hosted config (with embedded credentials) to the cloud offering, copying a URL straight out of a password manager or browser address bar that retains user:pass@, curl examples from homelab docs pasted verbatim.

Related errors


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