Module: Alap::SsrfGuard

Defined in:
lib/alap/ssrf_guard.rb

Overview

SSRF (Server-Side Request Forgery) guard.

Blocks requests to private/reserved IP ranges when the :web: protocol runs in a server-side context (AOT baker, SSR).

This is a syntactic check — it inspects the hostname string, not DNS.

Constant Summary collapse

PRIVATE_RANGES =
[
  IPAddr.new("10.0.0.0/8"),
  IPAddr.new("172.16.0.0/12"),
  IPAddr.new("192.168.0.0/16"),
  IPAddr.new("127.0.0.0/8"),
  IPAddr.new("169.254.0.0/16"),
  IPAddr.new("0.0.0.0/8"),
  IPAddr.new("100.64.0.0/10"),
  IPAddr.new("224.0.0.0/4"),     # multicast
  IPAddr.new("240.0.0.0/4"),     # reserved
  IPAddr.new("::1/128"),         # IPv6 loopback
  IPAddr.new("fe80::/10"),       # IPv6 link-local
  IPAddr.new("fc00::/7"),        # IPv6 unique local
  IPAddr.new("ff00::/8"),        # IPv6 multicast
].freeze

Class Method Summary collapse

Class Method Details

.private_host?(url) ⇒ Boolean

Returns true if url targets a private, reserved, or loopback host. Malformed URLs return true (fail closed).

Returns:

  • (Boolean)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/alap/ssrf_guard.rb', line 36

def self.private_host?(url)
  uri = URI.parse(url)
  hostname = uri.host
rescue URI::InvalidURIError
  return true
else
  return true if hostname.nil? || hostname.empty?

  # Strip IPv6 brackets
  hostname = hostname.delete_prefix("[").delete_suffix("]")

  # Localhost variants
  return true if hostname == "localhost" || hostname.end_with?(".localhost")

  # Try parsing as an IP address
  begin
    addr = IPAddr.new(hostname)
  rescue IPAddr::InvalidAddressError
    # Not an IP literal — regular domain name
    return false
  end

  # Check IPv4-mapped IPv6 (::ffff:x.x.x.x)
  if addr.ipv6? && addr.ipv4_mapped?
    addr = addr.native
  end

  PRIVATE_RANGES.any? { |range| range.include?(addr) }
end