instructure/canvas-lms · warning

CANVAS_HTTP WARNING insecure address | host: #

Error message

CANVAS_HTTP WARNING insecure address | host: #{host} | insecure_address: #{addr} | range: #{range}

What it means

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.

Solutions

  1. Use the public/external hostname for the resource, or bypass CanvasHttp for trusted internal calls with an explicitly allowlisted path
  2. Check the host's DNS resolution — a public domain resolving to a private IP usually indicates misconfiguration or rebinding
  3. Adjust blocked_ip_ranges only if a range is wrongly blocked for your network, via Canvas's CanvasHttp configuration, not by editing the gem
  4. For local development, run services on publicly-resolvable names or stub CanvasHttp in tests

Example fix

// before
CanvasHttp.get('http://internal-service.local/api') # resolves to 10.0.0.5 -> blocked
// after
# use the service's external endpoint or a dedicated non-SSRF-checked client for trusted internal calls
TrustedInternalHttp.get('http://internal-service.local/api')
Defensive patterns

Strategy: validation

Validate before calling

# pre-check the resolved address against CanvasHttp's blocked ranges
resolved = Resolv.getaddresses(host)
raise CanvasHttp::InsecureUriError if resolved.any? { |ip| CanvasHttp.blocked_ip?(ip) }

Try / catch

begin
  CanvasHttp.get(url)
rescue CanvasHttp::InsecureUriError
  # use an internal, SSRF-exempt client for trusted hosts
  TrustedInternalHttp.get(url)
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/117992d8c16c7f26. Report an issue: GitHub.

Appendix: source

Thrown at gems/canvas_http/lib/canvas_http.rb:294

    end

    ip_addrs = resolved_addrs.filter_map do |ip|
      ::IPAddr.new(ip)
    rescue IPAddr::InvalidAddressError
      # this should never happen, Resolv should only be passing back IPs, but
      # let's make sure we can see if the impossible occurs
      logger.warn("CANVAS_HTTP WARNING | host: #{host} | invalid_ip: #{ip}")
      nil
    end
    unless ip_addrs.any?
      raise UnresolvableUriError, "#{host} resolves to only unparseable IPs..."
    end

    blocked_ip_ranges.each do |range|
      addr_range = ::IPAddr.new(range)
      ip_addrs.any? do |addr|
        if addr_range.include?(addr)
          logger.warn("CANVAS_HTTP WARNING insecure address | host: #{host} | insecure_address: #{addr} | range: #{range}")
          return true
        end
      end
    end
    false
  end

  # returns a Net::HTTP connection object for the given URI object
  def self.connection_for_uri(uri)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = (uri.scheme == "https")
    http.ssl_timeout = http.open_timeout = OPEN_TIMEOUT
    http.read_timeout = READ_TIMEOUT
    http.write_timeout = WRITE_TIMEOUT
    # Don't rely on net/http's internal retries, since they swallow errors in a
    # way that can't be detected when streaming responses, leading to duplicate
    # data
    http.max_retries = 0

View on GitHub (pinned to 1c9f0bb801)