{"record":{"id":"117992d8c16c7f26","repo":"instructure/canvas-lms","slug":"canvas-http-warning-insecure-address-host-host-insecure","errorCode":null,"errorMessage":"CANVAS_HTTP WARNING insecure address | host: #{host} | insecure_address: #{addr} | range: #{range}","messagePattern":"CANVAS_HTTP WARNING insecure address \\| host: #(.+?) \\| insecure_address: #(.+?) \\| range: #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gems/canvas_http/lib/canvas_http.rb","lineNumber":294,"sourceCode":"    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)\n          logger.warn(\"CANVAS_HTTP WARNING insecure address | host: #{host} | insecure_address: #{addr} | range: #{range}\")\n          return true\n        end\n      end\n    end\n    false\n  end\n\n  # returns a Net::HTTP connection object for the given URI object\n  def self.connection_for_uri(uri)\n    http = Net::HTTP.new(uri.host, uri.port)\n    http.use_ssl = (uri.scheme == \"https\")\n    http.ssl_timeout = http.open_timeout = OPEN_TIMEOUT\n    http.read_timeout = READ_TIMEOUT\n    http.write_timeout = WRITE_TIMEOUT\n    # Don't rely on net/http's internal retries, since they swallow errors in a\n    # way that can't be detected when streaming responses, leading to duplicate\n    # data\n    http.max_retries = 0","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_http/lib/canvas_http.rb#L276-L312","documentation":"CanvasHttp checks resolved addresses against blocked_ip_ranges (localhost, private, link-local ranges) as SSRF protection. When a resolved address falls inside a blocked range, it logs this warning naming the host, the offending address, and the matching range, then returns true to signal the address is insecure.","triggerScenarios":"validate_address (host validation for outbound HTTP) resolves host to an addr that addr_range.include?(addr) matches for one of blocked_ip_ranges — e.g., a DNS name resolving to 127.0.0.1, 10.x.x.x, 169.254.x.x, or ::1.","commonSituations":"Internal hostnames like localhost.internal resolving to private IPs during local dev; DNS rebinding or misconfigured public DNS pointing a domain at a private IP; integrations configured with internal service URLs that CanvasHttp refuses to fetch.","solutions":["Use the public/external hostname for the resource, or bypass CanvasHttp for trusted internal calls with an explicitly allowlisted path","Check the host's DNS resolution — a public domain resolving to a private IP usually indicates misconfiguration or rebinding","Adjust blocked_ip_ranges only if a range is wrongly blocked for your network, via Canvas's CanvasHttp configuration, not by editing the gem","For local development, run services on publicly-resolvable names or stub CanvasHttp in tests"],"exampleFix":"// before\nCanvasHttp.get('http://internal-service.local/api') # resolves to 10.0.0.5 -> blocked\n// after\n# use the service's external endpoint or a dedicated non-SSRF-checked client for trusted internal calls\nTrustedInternalHttp.get('http://internal-service.local/api')","handlingStrategy":"validation","validationCode":"# pre-check the resolved address against CanvasHttp's blocked ranges\nresolved = Resolv.getaddresses(host)\nraise CanvasHttp::InsecureUriError if resolved.any? { |ip| CanvasHttp.blocked_ip?(ip) }","typeGuard":null,"tryCatchPattern":"begin\n  CanvasHttp.get(url)\nrescue CanvasHttp::InsecureUriError\n  # use an internal, SSRF-exempt client for trusted hosts\n  TrustedInternalHttp.get(url)\nend","preventionTips":["Never point Canvas-configured URLs at hostnames that resolve to private ranges","Check DNS resolution of integration hosts during environment provisioning","Use dedicated non-SSRF-checked clients only for explicitly trusted internal services"],"tags":["ssrf","security","ip-address","canvas-http"],"backgroundTag":"address-blocked","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"}