Coverage for src/local_deep_research/security/ip_ranges.py: 100%

4 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-22 21:20 +0000

1""" 

2Shared private/internal IP range constants for security validation. 

3 

4Used by SSRF validation and notification URL validation to avoid 

5duplicating IP range definitions. 

6""" 

7 

8import ipaddress 

9 

10# RFC 6598 shared address space, also used by download candidate filtering. 

11CGNAT_NETWORK = ipaddress.ip_network("100.64.0.0/10") 

12 

13# RFC1918 private networks + loopback + link-local + CGNAT + IPv6 equivalents 

14# nosec B104 - These hardcoded IPs are intentional for security validation 

15PRIVATE_IP_RANGES = [ 

16 ipaddress.ip_network("127.0.0.0/8"), # IPv4 loopback 

17 ipaddress.ip_network("::1/128"), # IPv6 loopback 

18 ipaddress.ip_network("10.0.0.0/8"), # RFC1918 Class A private 

19 ipaddress.ip_network("172.16.0.0/12"), # RFC1918 Class B private 

20 ipaddress.ip_network("192.168.0.0/16"), # RFC1918 Class C private 

21 CGNAT_NETWORK, # CGNAT - used by Podman/rootless containers 

22 ipaddress.ip_network("169.254.0.0/16"), # Link-local 

23 ipaddress.ip_network("fe80::/10"), # IPv6 link-local 

24 ipaddress.ip_network("fc00::/7"), # IPv6 unique local 

25 ipaddress.ip_network("0.0.0.0/8"), # "This" network (IPv4 unspecified) 

26 ipaddress.ip_network( 

27 "::/128" 

28 ), # IPv6 unspecified — Linux routes connections to local host 

29 # IPv6 transition prefixes that can wrap private IPv4 destinations. 

30 # On Linux hosts with kernel sit0 / NAT64 routes configured, these 

31 # prefixes are forwarded to the embedded IPv4 (e.g. 2002:7f00:1:: 

32 # → 127.0.0.1 via 6to4). Default Linux has no such routes so they 

33 # are not exploitable in the typical deployment, but blocking them 

34 # closes the gap for operators who do enable transition tunnels. 

35 ipaddress.ip_network("2002::/16"), # 6to4 (RFC 3056, deprecated RFC 7526) 

36 ipaddress.ip_network("64:ff9b::/96"), # NAT64 well-known prefix (RFC 6052) 

37 ipaddress.ip_network( 

38 "64:ff9b:1::/48" 

39 ), # NAT64 local-use prefix (RFC 8215) — same SSRF threat class as the WKP 

40 ipaddress.ip_network("2001::/32"), # Teredo (RFC 4380) 

41 ipaddress.ip_network("100::/64"), # IPv6 discard prefix (RFC 6666) 

42 # IPv4-Compatible IPv6 — DEPRECATED by RFC 4291 §2.5.5.1 in 2006 but 

43 # still parseable by ipaddress and routable on hosts with ::/96 routes 

44 # configured (rare but real). Embeds the IPv4 in the low 32 bits so 

45 # [::169.254.169.254] would otherwise reach IMDS, identically to the 

46 # 6to4/NAT64 wraps. Has zero legitimate live use; blocking it is the 

47 # same defense-in-depth move as the transition prefixes above. 

48 ipaddress.ip_network("::/96"), 

49 # IPv4-Translated (RFC 2765 / SIIT). Same shape as the IPv4-Compatible 

50 # prefix above — the IPv4 sits in the low 32 bits — and the same threat: 

51 # `::ffff:0:169.254.169.254` reaches IMDS on a host with SIIT routes. 

52 # NOT covered by the validator's `ipv4_mapped` unwrap, which only 

53 # recognises the MAPPED form `::ffff:0:0/96` (`::ffff:127.0.0.1`); 

54 # `ipaddress` returns None for `ipv4_mapped` on the translated form, so 

55 # it fell through every IPv4 check and classified as public (#6408). 

56 ipaddress.ip_network("::ffff:0:0:0/96"), 

57] 

58 

59# NAT64 prefixes — operators on IPv6-only hosts using DNS64+NAT64 reach 

60# IPv4 services through these. Blocking by default protects the typical 

61# deployment shape (laptops / dual-stack) from the IPv6-wrapped IMDS / 

62# RFC1918 SSRF bypass class. Operators who actually need NAT64 reachable 

63# can opt in via the env-only setting ``security.allow_nat64`` 

64# (LDR_SECURITY_ALLOW_NAT64=true). 6to4, Teredo, discard, the 

65# IPv4-Compatible prefix (::/96, except ::1 loopback which follows the 

66# loopback/private flags), and the IPv4-Translated/SIIT prefix 

67# (::ffff:0:0:0/96) remain unconditionally blocked — they have no 

68# legitimate live use. 

69NAT64_PREFIXES = [ 

70 ipaddress.ip_network("64:ff9b::/96"), 

71 ipaddress.ip_network("64:ff9b:1::/48"), 

72]