from __future__ import annotations import ipaddress from ipaddress import IPv4Address, IPv6Address # AWS/GCP/Azure-style metadata endpoints commonly abused for SSRF METADATA_HOSTNAMES = frozenset( { "metadata.google.internal", "metadata.goog", "kubernetes.default", "kubernetes.default.svc", } ) def is_blocked_hostname(hostname: str) -> bool: host = hostname.strip(".").lower() if host in METADATA_HOSTNAMES: return True if host.endswith(".local") or host.endswith(".localhost"): return True return host in {"localhost", "metadata"} def is_public_ip(ip_str: str) -> bool: """Return True if the IP is globally routable / safe to connect to.""" try: ip = ipaddress.ip_address(ip_str) except ValueError: return False return not _is_blocked_ip(ip) def _is_blocked_ip(ip: IPv4Address | IPv6Address) -> bool: if ip.is_loopback: return True if ip.is_private: return True if ip.is_link_local: return True if ip.is_multicast: return True if ip.is_reserved: return True if ip.is_unspecified: return True # CGNAT / documentation / benchmarking often flagged via is_private or reserved; # also block classic metadata IPv4 explicitly. if isinstance(ip, IPv4Address): if ip == IPv4Address("169.254.169.254"): return True if ip in ipaddress.ip_network("0.0.0.0/8"): return True if isinstance(ip, IPv6Address): # Unique local addresses if ip.ipv4_mapped is not None: return _is_blocked_ip(ip.ipv4_mapped) if (int(ip) >> 118) == 0b1111110: # fc00::/7 return True return False def assert_safe_destination(hostname: str, resolved_ips: list[str]) -> None: """Raise ValueError if hostname or any resolved IP is unsafe.""" if is_blocked_hostname(hostname): raise ValueError(f"blocked hostname: {hostname}") if not resolved_ips: raise ValueError("no resolved addresses") for ip in resolved_ips: if not is_public_ip(ip): raise ValueError(f"blocked address: {ip}") def assert_connected_ip_safe(ip_str: str) -> None: """Re-check the address actually used for the connection (anti-rebinding).""" if not is_public_ip(ip_str): raise ValueError(f"blocked connected address: {ip_str}")