{"record":{"id":"c0bc447523ae5795","repo":"Freika/dawarich","slug":"url-must-not-embed-credentials-user-pass-host","errorCode":null,"errorMessage":"URL must not embed credentials (user:pass@host)","messagePattern":"URL must not embed credentials \\(user:pass@host\\)","errorType":"validation","errorClass":"UrlValidatable::BlockedUrlError","httpStatus":null,"severity":"warning","filePath":"app/services/concerns/url_validatable.rb","lineNumber":72,"sourceCode":"    IPAddr.new('fc00::/7')         # IPv6 ULA\n  ].freeze\n\n  private\n\n  def validate_integration_url!(url)\n    return if url.blank?\n\n    uri = URI.parse(url)\n    unless %w[http https].include?(uri.scheme)\n      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_scheme', scheme: uri.scheme)\n    end\n    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.host_required') if uri.host.blank?\n\n    # Cloud refuses URLs that embed credentials. Self-hosters legitimately\n    # use http://user:pass@host — homelab Immich behind nginx basic-auth\n    # is a real config we don't want to break.\n    if uri.userinfo.present? && !DawarichSettings.self_hosted?\n      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.embedded_credentials')\n    end\n\n    ip = IPAddr.new(Resolv.getaddress(uri.host))\n    if blocked_ranges.any? { |range| range.include?(ip) }\n      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.blocked_address')\n    end\n  rescue URI::InvalidURIError\n    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_format')\n  rescue Resolv::ResolvError\n    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.unresolvable_host', host: uri.host)\n  end\n\n  def blocked_ranges\n    if DawarichSettings.self_hosted?\n      ALWAYS_BLOCKED_RANGES\n    else\n      ALWAYS_BLOCKED_RANGES + CLOUD_ONLY_BLOCKED_RANGES\n    end","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/services/concerns/url_validatable.rb#L54-L90","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the credentials from the URL and configure auth the supported way (API key field / header-based auth for the integration).","If you are running your own instance, confirm self_hosted? is true in DawarichSettings — then this check is skipped by design.","Never paste user:pass URLs into cloud settings; rotate those credentials if you already did."],"exampleFix":"# before (cloud instance)\nurl = 'https://alice:hunter2@immich.example.com' # -> BlockedUrlError\n\n# after\nurl = 'https://immich.example.com'\n# supply auth via the integration's API key configuration instead","handlingStrategy":"validation","validationCode":"uri = URI.parse(url.to_s)\nuri.userinfo.nil? || DawarichSettings.self_hosted? # warn cloud users pre-save","typeGuard":"def credential_free_url?(s)\n  URI.parse(s.to_s).userinfo.nil?\nrescue URI::InvalidURIError\n  false\nend","tryCatchPattern":"begin\n  validate_integration_url!(url)\nrescue BlockedUrlError => e\n  # strip user:pass@ and configure API-key auth instead\n  url = url.sub(%r{\\A([a-z]+://)[^@/]+@}i, '\\1')\n  retry\nend","preventionTips":["Never put basic-auth credentials in integration URLs on hosted platforms; use the API key field.","Strip userinfo before saving (regex sub) as a convenience normalization.","Rotate any credentials that were pasted into cloud settings."],"tags":["url-validation","security","credentials","integrations","ruby"],"backgroundTag":"url-embedded-credentials","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}