Module: LcpRuby::UrlSafety
- Defined in:
- lib/lcp_ruby/url_safety.rb
Overview
Shared URL safety utilities for outbound links and HTTP requests. Used by UrlLink renderer (display) and CallWebhookAction (background jobs).
Defined Under Namespace
Classes: UnsafeUrlError
Constant Summary collapse
- SAFE_LINK_SCHEMES =
%w[http https].freeze
- SAFE_HTTP_SCHEMES =
%w[http https].freeze
- BLOCKED_NETWORKS =
[ IPAddr.new("0.0.0.0/8"), IPAddr.new("10.0.0.0/8"), IPAddr.new("100.64.0.0/10"), IPAddr.new("127.0.0.0/8"), IPAddr.new("169.254.0.0/16"), IPAddr.new("172.16.0.0/12"), IPAddr.new("192.168.0.0/16"), IPAddr.new("198.18.0.0/15"), IPAddr.new("224.0.0.0/4"), IPAddr.new("240.0.0.0/4"), IPAddr.new("::/128"), IPAddr.new("::1/128"), IPAddr.new("fc00::/7"), IPAddr.new("fe80::/10"), IPAddr.new("ff00::/8") ].freeze
Class Method Summary collapse
- .blocked_address?(address) ⇒ Boolean
- .ip_literal?(host) ⇒ Boolean
- .localhost?(host) ⇒ Boolean
- .reject_host!(host) ⇒ Object
- .resolve_addresses(host) ⇒ Object
-
.safe_external_link?(value) ⇒ Boolean
Check whether a value is safe to render as an <a href=“…”> link.
-
.validate_outbound_http_url!(value) ⇒ Object
Validate a URL for outbound HTTP requests (webhooks, etc.).
Class Method Details
.blocked_address?(address) ⇒ Boolean
93 94 95 |
# File 'lib/lcp_ruby/url_safety.rb', line 93 def blocked_address?(address) BLOCKED_NETWORKS.any? { |network| network.include?(address) } end |
.ip_literal?(host) ⇒ Boolean
81 82 83 84 85 86 |
# File 'lib/lcp_ruby/url_safety.rb', line 81 def ip_literal?(host) IPAddr.new(host) true rescue IPAddr::InvalidAddressError false end |
.localhost?(host) ⇒ Boolean
88 89 90 91 |
# File 'lib/lcp_ruby/url_safety.rb', line 88 def localhost?(host) normalized = host.to_s.downcase normalized == "localhost" || normalized.end_with?(".localhost") end |
.reject_host!(host) ⇒ Object
61 62 63 64 65 66 67 |
# File 'lib/lcp_ruby/url_safety.rb', line 61 def reject_host!(host) raise UnsafeUrlError, "URL host is not allowed" if localhost?(host) addresses = resolve_addresses(host) raise UnsafeUrlError, "URL host could not be resolved" if addresses.empty? raise UnsafeUrlError, "URL host resolves to a private or loopback address" if addresses.any? { |address| blocked_address?(address) } end |
.resolve_addresses(host) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/lcp_ruby/url_safety.rb', line 69 def resolve_addresses(host) return [ IPAddr.new(host) ] if ip_literal?(host) Resolv.getaddresses(host).filter_map do |address| IPAddr.new(address) rescue IPAddr::InvalidAddressError nil end rescue Resolv::ResolvError, SocketError [] end |
.safe_external_link?(value) ⇒ Boolean
Check whether a value is safe to render as an <a href=“…”> link. Returns true only for http/https URLs with a host present.
35 36 37 38 39 40 41 42 43 |
# File 'lib/lcp_ruby/url_safety.rb', line 35 def safe_external_link?(value) return false if value.blank? href = value.to_s.strip uri = URI.parse(href) SAFE_LINK_SCHEMES.include?(uri.scheme.to_s.downcase) && uri.host.present? rescue URI::InvalidURIError false end |
.validate_outbound_http_url!(value) ⇒ Object
Validate a URL for outbound HTTP requests (webhooks, etc.). Raises UnsafeUrlError if the URL is not safe. Returns the parsed URI on success.
48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/lcp_ruby/url_safety.rb', line 48 def validate_outbound_http_url!(value) uri = URI.parse(value.to_s) scheme = uri.scheme.to_s.downcase raise UnsafeUrlError, "URL must use http or https" unless SAFE_HTTP_SCHEMES.include?(scheme) raise UnsafeUrlError, "URL host is missing" if uri.host.to_s.empty? reject_host!(uri.host) uri rescue URI::InvalidURIError => e raise UnsafeUrlError, "Invalid URL: #{e.}" end |