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
- 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
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
- 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
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
- CANVAS_HTTP WARNING | host: #
- CANVAS_HTTP CB_TRIP ON #
- assets_url host for '#
- Attachment verifier token id mismatch. token id: #
- Attachment verifier token invalid: #
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 = 0View on GitHub (pinned to 1c9f0bb801)