@@ -54,19 +54,44 @@ def test_ipv4_mapped_ipv6_blocked_if_private(self):
5454 @pytest .mark .parametrize (
5555 "address" ,
5656 [
57- pytest .param ("64:ff9b::7f00:1" , id = "loopback" ),
58- pytest .param ("64:ff9b::0a00:1" , id = "private" ),
59- pytest .param ("64:ff9b::a9fe:a9fe" , id = "link-local" ),
60- pytest .param ("64:ff9b::6440:1" , id = "cgnat" ),
57+ pytest .param ("64:ff9b::7f00:1" , id = "nat64-loopback" ),
58+ pytest .param ("64:ff9b::0a00:1" , id = "nat64-private" ),
59+ pytest .param ("64:ff9b::a9fe:a9fe" , id = "nat64-link-local" ),
60+ pytest .param ("64:ff9b::6440:1" , id = "nat64-cgnat" ),
61+ pytest .param ("64:ff9b:1::a9fe:a9fe" , id = "nat64-local-use-low32" ),
62+ pytest .param ("64:ff9b:1:a9fe:a9:fe00::" , id = "nat64-local-use-48" ),
63+ pytest .param ("::ffff:0:7f00:1" , id = "ipv4-translated-loopback" ),
64+ pytest .param ("::ffff:0:0a00:1" , id = "ipv4-translated-private" ),
65+ pytest .param ("::ffff:0:a9fe:a9fe" , id = "ipv4-translated-link-local" ),
66+ pytest .param ("::ffff:0:6440:1" , id = "ipv4-translated-cgnat" ),
67+ pytest .param ("::7f00:1" , id = "ipv4-compatible-loopback" ),
68+ pytest .param ("::0a00:1" , id = "ipv4-compatible-private" ),
69+ pytest .param ("::a9fe:a9fe" , id = "ipv4-compatible-link-local" ),
70+ pytest .param ("::6440:1" , id = "ipv4-compatible-cgnat" ),
71+ pytest .param ("2002:a9fe:a9fe::1" , id = "6to4-link-local" ),
72+ pytest .param ("2606:4700::5efe:192.168.1.1" , id = "isatap-private" ),
73+ pytest .param (
74+ "2606:4700::200:5efe:169.254.169.254" ,
75+ id = "isatap-link-local" ,
76+ ),
6177 ],
6278 )
63- def test_nat64_ipv6_blocked_if_embedded_ipv4_blocked (self , address : str ):
64- """NAT64 IPv6 addresses should check the embedded IPv4."""
79+ def test_ipv6_transition_blocked_if_embedded_ipv4_blocked (self , address : str ):
80+ """IPv6 transition addresses should check the embedded IPv4."""
6581 assert is_ip_allowed (address ) is False
6682
67- def test_nat64_ipv6_allowed_if_embedded_ipv4_allowed (self ):
68- """NAT64 IPv6 addresses should stay allowed for public embedded IPv4."""
69- assert is_ip_allowed ("64:ff9b::0808:0808" ) is True
83+ @pytest .mark .parametrize (
84+ "address" ,
85+ [
86+ pytest .param ("64:ff9b::0808:0808" , id = "nat64" ),
87+ pytest .param ("::ffff:0:0808:0808" , id = "ipv4-translated" ),
88+ pytest .param ("::0808:0808" , id = "ipv4-compatible" ),
89+ pytest .param ("2606:4700::5efe:8.8.8.8" , id = "isatap" ),
90+ ],
91+ )
92+ def test_ipv6_transition_allowed_if_embedded_ipv4_allowed (self , address : str ):
93+ """IPv6 transition addresses should allow public embedded IPv4."""
94+ assert is_ip_allowed (address ) is True
7095
7196
7297class TestValidateURL :
@@ -100,11 +125,21 @@ async def test_private_ip_rejected(self):
100125 with pytest .raises (SSRFError , match = "blocked IP" ):
101126 await validate_url ("https://example.com/path" )
102127
103- async def test_nat64_private_ip_rejected (self ):
104- """URLs resolving to NAT64-wrapped private IPs should be rejected."""
128+ @pytest .mark .parametrize (
129+ "address" ,
130+ [
131+ pytest .param ("64:ff9b::0a00:1" , id = "nat64" ),
132+ pytest .param ("64:ff9b:1:a9fe:a9:fe00::" , id = "nat64-local-use" ),
133+ pytest .param ("::ffff:0:a9fe:a9fe" , id = "ipv4-translated" ),
134+ pytest .param ("::a9fe:a9fe" , id = "ipv4-compatible" ),
135+ pytest .param ("2606:4700::5efe:169.254.169.254" , id = "isatap" ),
136+ ],
137+ )
138+ async def test_ipv6_transition_private_ip_rejected (self , address : str ):
139+ """URLs resolving to IPv6-wrapped private IPs should be rejected."""
105140 with patch (
106141 "fastmcp.server.auth.ssrf.resolve_hostname" ,
107- return_value = ["64:ff9b::0a00:1" ],
142+ return_value = [address ],
108143 ):
109144 with pytest .raises (SSRFError , match = "blocked IP" ):
110145 await validate_url ("https://example.com/path" )
0 commit comments