Skip to content

Commit c878403

Browse files
⏪ revert URL safety checks (#455)
1 parent 3655fe0 commit c878403

2 files changed

Lines changed: 1 addition & 143 deletions

File tree

mindee/mindee_http/response_validation.py

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import ipaddress
21
import json
32
from urllib.parse import urlparse
43

@@ -7,25 +6,12 @@
76
from mindee.error.mindee_error import MindeeSourceError
87
from mindee.parsing.common.string_dict import StringDict
98

10-
_CGNAT_BLOCK = ipaddress.IPv4Network("100.64.0.0/10")
11-
_IPV6_UNIQUE_LOCAL = ipaddress.IPv6Network("fc00::/7")
12-
139

1410
def validate_url_for_source(url: str) -> None:
1511
"""
1612
Validates that a URL is safe to send to the Mindee server.
1713
18-
Rejects any URL that could be used for Server-Side Request Forgery (SSRF):
19-
20-
- non-HTTPS schemes,
21-
- embedded userinfo (e.g. ``https://user:pass@host``),
22-
- loopback hostnames (``localhost``, ``*.localhost``),
23-
- literal IP addresses that are loopback, link-local, private (RFC 1918),
24-
any-local (``0.0.0.0``), multicast, IPv6 unique-local (``fc00::/7``),
25-
or carrier-grade NAT (``100.64.0.0/10``).
26-
27-
Note: DNS resolution is not performed. A hostname that resolves to a
28-
private IP will not be caught here.
14+
Rejects any URL that follow non-HTTPS schemes.
2915
3016
:param url: The URL string to validate.
3117
:raises MindeeSourceError: If the URL fails any security check.
@@ -38,42 +24,6 @@ def validate_url_for_source(url: str) -> None:
3824
if parsed.scheme.lower() != "https":
3925
raise MindeeSourceError("URL must be HTTPS")
4026

41-
if parsed.username or parsed.password:
42-
raise MindeeSourceError("Source URLs must not embed user credentials")
43-
44-
host = parsed.hostname
45-
if not host:
46-
raise MindeeSourceError("Source URL is missing a host")
47-
48-
lower_host = host.lower()
49-
if (
50-
lower_host == "localhost"
51-
or lower_host.endswith(".localhost")
52-
or lower_host == "ip6-localhost"
53-
or lower_host == "ip6-loopback"
54-
):
55-
raise MindeeSourceError(f"Loopback hostnames are not allowed: {host}")
56-
57-
try:
58-
addr = ipaddress.ip_address(lower_host)
59-
except ValueError:
60-
return
61-
62-
if (
63-
addr.is_loopback
64-
or addr.is_link_local
65-
or addr.is_private
66-
or addr.is_unspecified
67-
or addr.is_multicast
68-
):
69-
raise MindeeSourceError(f"URL host resolves to a disallowed address: {addr}")
70-
71-
if isinstance(addr, ipaddress.IPv4Address) and addr in _CGNAT_BLOCK:
72-
raise MindeeSourceError(f"URL host resolves to a disallowed address: {addr}")
73-
74-
if isinstance(addr, ipaddress.IPv6Address) and addr in _IPV6_UNIQUE_LOCAL:
75-
raise MindeeSourceError(f"URL host resolves to a disallowed address: {addr}")
76-
7727

7828
def is_valid_sync_response(response: httpx.Response) -> bool:
7929
"""

tests/v1/input/test_url_validation.py

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -15,95 +15,3 @@ def test_rejects_ftp(self):
1515

1616
def test_accepts_https(self):
1717
validate_url_for_source("https://example.com/file.pdf")
18-
19-
20-
class TestValidateUrlUserinfo:
21-
def test_rejects_username_and_password(self):
22-
with pytest.raises(MindeeSourceError, match="credentials"):
23-
validate_url_for_source("https://user:pass@example.com/file.pdf")
24-
25-
def test_rejects_username_only(self):
26-
with pytest.raises(MindeeSourceError, match="credentials"):
27-
validate_url_for_source("https://user@example.com/file.pdf")
28-
29-
30-
class TestValidateUrlLoopbackHostnames:
31-
def test_rejects_localhost(self):
32-
with pytest.raises(MindeeSourceError, match="Loopback"):
33-
validate_url_for_source("https://localhost/file.pdf")
34-
35-
def test_rejects_localhost_subdomain(self):
36-
with pytest.raises(MindeeSourceError, match="Loopback"):
37-
validate_url_for_source("https://myapp.localhost/file.pdf")
38-
39-
def test_rejects_ip6_localhost(self):
40-
with pytest.raises(MindeeSourceError, match="Loopback"):
41-
validate_url_for_source("https://ip6-localhost/file.pdf")
42-
43-
def test_rejects_ip6_loopback(self):
44-
with pytest.raises(MindeeSourceError, match="Loopback"):
45-
validate_url_for_source("https://ip6-loopback/file.pdf")
46-
47-
48-
class TestValidateUrlLoopbackIPs:
49-
def test_rejects_ipv4_loopback(self):
50-
with pytest.raises(MindeeSourceError, match="disallowed"):
51-
validate_url_for_source("https://127.0.0.1/file.pdf")
52-
53-
def test_rejects_ipv4_loopback_other(self):
54-
with pytest.raises(MindeeSourceError, match="disallowed"):
55-
validate_url_for_source("https://127.0.0.2/file.pdf")
56-
57-
def test_rejects_ipv6_loopback(self):
58-
with pytest.raises(MindeeSourceError, match="disallowed"):
59-
validate_url_for_source("https://[::1]/file.pdf")
60-
61-
62-
class TestValidateUrlPrivateIPs:
63-
def test_rejects_rfc1918_10_block(self):
64-
with pytest.raises(MindeeSourceError, match="disallowed"):
65-
validate_url_for_source("https://10.0.0.1/file.pdf")
66-
67-
def test_rejects_rfc1918_172_block(self):
68-
with pytest.raises(MindeeSourceError, match="disallowed"):
69-
validate_url_for_source("https://172.16.0.1/file.pdf")
70-
71-
def test_rejects_rfc1918_192_block(self):
72-
with pytest.raises(MindeeSourceError, match="disallowed"):
73-
validate_url_for_source("https://192.168.1.1/file.pdf")
74-
75-
def test_rejects_link_local(self):
76-
with pytest.raises(MindeeSourceError, match="disallowed"):
77-
validate_url_for_source("https://169.254.0.1/file.pdf")
78-
79-
def test_rejects_unspecified(self):
80-
with pytest.raises(MindeeSourceError, match="disallowed"):
81-
validate_url_for_source("https://0.0.0.0/file.pdf")
82-
83-
def test_rejects_multicast(self):
84-
with pytest.raises(MindeeSourceError, match="disallowed"):
85-
validate_url_for_source("https://224.0.0.1/file.pdf")
86-
87-
88-
class TestValidateUrlCgnat:
89-
def test_rejects_cgnat_start(self):
90-
with pytest.raises(MindeeSourceError, match="disallowed"):
91-
validate_url_for_source("https://100.64.0.1/file.pdf")
92-
93-
def test_rejects_cgnat_end(self):
94-
with pytest.raises(MindeeSourceError, match="disallowed"):
95-
validate_url_for_source("https://100.127.255.255/file.pdf")
96-
97-
def test_accepts_just_outside_cgnat(self):
98-
# 100.128.0.1 is outside 100.64.0.0/10
99-
validate_url_for_source("https://100.128.0.1/file.pdf")
100-
101-
102-
class TestValidateUrlIpv6UniqueLocal:
103-
def test_rejects_ipv6_ula_fc(self):
104-
with pytest.raises(MindeeSourceError, match="disallowed"):
105-
validate_url_for_source("https://[fc00::1]/file.pdf")
106-
107-
def test_rejects_ipv6_ula_fd(self):
108-
with pytest.raises(MindeeSourceError, match="disallowed"):
109-
validate_url_for_source("https://[fd00::1]/file.pdf")

0 commit comments

Comments
 (0)