{"record":{"id":"0f3aff3ecd4f36b2","repo":"instructure/canvas-lms","slug":"host-cannot-be-resolved-to-any-address","errorCode":null,"errorMessage":"#{host} cannot be resolved to any address","messagePattern":"#(.+?) cannot be resolved to any address","errorType":"exception","errorClass":"UnresolvableUriError","httpStatus":null,"severity":"error","filePath":"gems/canvas_http/lib/canvas_http.rb","lineNumber":275,"sourceCode":"        value = \"#{scheme}://#{value}\"\n      end\n      uri = URI.parse(value) # it's still a URI::Generic\n    end\n    raise ArgumentError if !allowed_schemes.nil? && !allowed_schemes.include?(uri.scheme.downcase)\n    raise(RelativeUriError) if uri.host.nil? || uri.host.strip.empty?\n    raise InsecureUriError if check_host && insecure_host?(uri.host)\n\n    [value, uri]\n  end\n\n  def self.insecure_host?(host)\n    return false if blocked_ip_ranges.empty?\n\n    resolved_addrs = Resolv.getaddresses(host)\n    unless resolved_addrs.any?\n      # this is actually a different condition than the host being insecure,\n      # and having separate telemetry is helpful for understanding transient failures.\n      raise UnresolvableUriError, \"#{host} cannot be resolved to any address\"\n    end\n\n    ip_addrs = resolved_addrs.filter_map do |ip|\n      ::IPAddr.new(ip)\n    rescue IPAddr::InvalidAddressError\n      # this should never happen, Resolv should only be passing back IPs, but\n      # let's make sure we can see if the impossible occurs\n      logger.warn(\"CANVAS_HTTP WARNING | host: #{host} | invalid_ip: #{ip}\")\n      nil\n    end\n    unless ip_addrs.any?\n      raise UnresolvableUriError, \"#{host} resolves to only unparseable IPs...\"\n    end\n\n    blocked_ip_ranges.each do |range|\n      addr_range = ::IPAddr.new(range)\n      ip_addrs.any? do |addr|\n        if addr_range.include?(addr)","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_http/lib/canvas_http.rb#L257-L293","documentation":"CanvasHttp's host-blocklist check resolves the hostname via Resolv.getaddresses before comparing against blocked IP ranges. If DNS returns no addresses at all, the host cannot be validated, so instead of silently proceeding it raises UnresolvableUriError. This prevents SSRF protection from being bypassed by unresolvable hosts.","triggerScenarios":"CanvasHttp.get/post (or other request helpers) called with a URL whose host fails DNS resolution: nonexistent domain, DNS outage, /etc/hosts-only names, or typoed internal hostnames when blocked_ip_ranges is non-empty.","commonSituations":"Fetching webhook/target URLs with stale or misconfigured DNS; container environments lacking DNS; typos in configured service hostnames; intermittent DNS transient failures during fetch jobs.","solutions":["Verify the hostname with `dig`/`nslookup` or Resolv.getaddresses(host) and fix the typo if the domain is wrong","Fix DNS configuration (resolv.conf, container network, VPC DNS) so the host resolves","If the host is internal, add it to DNS or /etc/hosts on the app servers, then retry","Rescue UnresolvableUriError in the caller and treat it as a retryable/transient fetch failure"],"exampleFix":"// before\nCanvasHttp.get('https://servcie.internal.example.com/api')\n// after\nhost = 'service.internal.example.com'\nraise ArgumentError, 'unresolvable host' if Resolv.getaddresses(host).empty?\nCanvasHttp.get(\"https://#{host}/api\")","handlingStrategy":"retry","validationCode":"resolved = Resolv.getaddresses(host)\nraise ArgumentError, 'host does not resolve' if resolved.empty?","typeGuard":"def resolvable?(host) = Resolv.getaddresses(host).any?","tryCatchPattern":"begin\n  CanvasHttp.get(url)\nrescue CanvasHttp::UnresolvableUriError => e\n  logger.warn(\"DNS failure for #{e.message}\")\n  retry_later(url)\nend","preventionTips":["Monitor DNS health in app containers/hosts","Validate configured hostnames at boot","Use fully-qualified domain names for internal services"],"tags":["network","dns","ssrf-protection"],"backgroundTag":"dns-resolution-failed","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}