instructure/canvas-lms · warning
CANVAS_HTTP WARNING | host: #
Error message
CANVAS_HTTP WARNING | host: #{host} | invalid_ip: #{ip} What it means
CanvasHttp's SSRF protection resolves the host and parses each resolved address into IPAddr objects. If Resolv ever returns a value that isn't a parseable IP, this warning is logged per-address and the entry is dropped. If no addresses remain parseable, an UnresolvableUriError is raised. The warning itself means the 'impossible' case occurred.
Solutions
- Inspect what Resolv returns for the host (`Resolv.getaddresses host`) and fix the resolver/DNS config
- Strip IPv6 zone identifiers before validation if hosts contain %scope suffixes
- Update the parsing to normalize addresses (e.g., strip brackets from bracketed IPv6 literals) if applicable
- If only the warning fires but some IPs parse, behavior is safe — but investigate why Resolv emitted a non-IP
Example fix
// before
ip = 'fe80::1%eth0'
IPAddr.new(ip) # InvalidAddressError -> warning
// after
ip = ip.split('%').first
IPAddr.new(ip) # parses link-local address
Defensive patterns
Strategy: validation
Validate before calling
def parseable_ip?(addr) IPAddr.new(addr.to_s.sub(/%.*$/, '')) true rescue IPAddr::InvalidAddressError false end
Type guard
def valid_ip?(str) IPAddr.new(str.to_s) true rescue IPAddr::InvalidAddressError false end
Try / catch
begin
CanvasHttp.get(url)
rescue CanvasHttp::UnresolvableUriError => e
logger.error("host unresolvable/invalid: #{e.message}")
end Prevention
- Strip IPv6 zone identifiers and brackets before DNS-related validation
- Test CanvasHttp with exotic hostnames (IPv6 literals, hosts-file entries)
- Keep Resolv output expectations documented when stubbing DNS in tests
When it happens
Trigger: resolved_addrs contains a string that IPAddr.new cannot parse (IPAddr::InvalidAddressError) while validating host before an outbound fetch — typically due to odd resolver output, IPv6 zone IDs, or custom DNS returning non-IP strings.
Common situations: Hostnames resolving through unusual resolver configurations; IPv6 literals with scope/zone identifiers (fe80::1%eth0); mocking/stubbing Resolv in tests with fake address strings; hosts-file entries with malformed values.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- CANVAS_HTTP WARNING insecure address | host: #
- CANVAS_HTTP CB_TRIP ON #
- # cannot be resolved to any address
- # resolves to only unparseable IPs...
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/3d8b542644ba608c.
Report an issue: GitHub.
Appendix: source
Thrown at gems/canvas_http/lib/canvas_http.rb:283
[value, uri]
end
def self.insecure_host?(host)
return false if blocked_ip_ranges.empty?
resolved_addrs = Resolv.getaddresses(host)
unless resolved_addrs.any?
# this is actually a different condition than the host being insecure,
# and having separate telemetry is helpful for understanding transient failures.
raise UnresolvableUriError, "#{host} cannot be resolved to any address"
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
View on GitHub (pinned to 1c9f0bb801)