Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

635 lines
22 KiB
Python

"""
Extended tests for the SSRF validator module.
Provides comprehensive coverage of is_ip_blocked, get_safe_url, validate_url,
and module constants (ALWAYS_BLOCKED_METADATA_IPS, ALLOWED_SCHEMES).
"""
from unittest.mock import patch
import pytest
from local_deep_research.security.ssrf_validator import (
ALWAYS_BLOCKED_METADATA_IPS,
ALLOWED_SCHEMES,
get_safe_url,
is_ip_blocked,
validate_url,
)
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
class TestAlwaysBlockedMetadataIPsConstant:
"""Verify the ALWAYS_BLOCKED_METADATA_IPS frozenset."""
def test_contains_aws_imds(self):
"""AWS IMDS / Azure / OCI / DigitalOcean shared endpoint."""
assert "169.254.169.254" in ALWAYS_BLOCKED_METADATA_IPS
def test_contains_aws_ecs_v3_and_v4(self):
assert "169.254.170.2" in ALWAYS_BLOCKED_METADATA_IPS
assert "169.254.170.23" in ALWAYS_BLOCKED_METADATA_IPS
def test_contains_alibaba_and_tencent(self):
assert "100.100.100.200" in ALWAYS_BLOCKED_METADATA_IPS
assert "169.254.0.23" in ALWAYS_BLOCKED_METADATA_IPS
class TestAllowedSchemesConstant:
"""Verify the ALLOWED_SCHEMES constant."""
def test_http_in_allowed_schemes(self):
"""http must be an allowed scheme."""
assert "http" in ALLOWED_SCHEMES
def test_https_in_allowed_schemes(self):
"""https must be an allowed scheme."""
assert "https" in ALLOWED_SCHEMES
def test_ftp_not_in_allowed_schemes(self):
"""ftp must NOT be an allowed scheme."""
assert "ftp" not in ALLOWED_SCHEMES
def test_file_not_in_allowed_schemes(self):
"""file must NOT be an allowed scheme."""
assert "file" not in ALLOWED_SCHEMES
def test_allowed_schemes_is_a_set(self):
"""ALLOWED_SCHEMES should be a set for O(1) lookups."""
assert isinstance(ALLOWED_SCHEMES, set)
def test_allowed_schemes_contains_only_expected_values(self):
"""ALLOWED_SCHEMES should contain exactly http and https."""
assert ALLOWED_SCHEMES == {"http", "https"}
# ---------------------------------------------------------------------------
# is_ip_blocked -- public / safe IPs
# ---------------------------------------------------------------------------
class TestIsIpBlockedPublicIPs:
"""Public IPs must never be blocked regardless of flags."""
@pytest.mark.parametrize(
"ip",
[
"8.8.8.8",
"8.8.4.4",
"1.1.1.1",
"1.0.0.1",
"208.67.222.222",
"142.250.185.206",
"93.184.216.34",
"198.51.100.1",
"203.0.113.5",
],
)
def test_public_ipv4_not_blocked(self, ip):
"""Well-known public IPv4 addresses are not blocked."""
assert is_ip_blocked(ip) is False
@pytest.mark.parametrize(
"ip",
[
"8.8.8.8",
"1.1.1.1",
"93.184.216.34",
],
)
def test_public_ipv4_not_blocked_with_allow_localhost(self, ip):
"""Public IPs remain unblocked when allow_localhost=True."""
assert is_ip_blocked(ip, allow_localhost=True) is False
@pytest.mark.parametrize(
"ip",
[
"8.8.8.8",
"1.1.1.1",
],
)
def test_public_ipv4_not_blocked_with_allow_private_ips(self, ip):
"""Public IPs remain unblocked when allow_private_ips=True."""
assert is_ip_blocked(ip, allow_private_ips=True) is False
def test_public_ipv6_not_blocked(self):
"""Public IPv6 addresses are not blocked."""
# Google public DNS IPv6
assert is_ip_blocked("2001:4860:4860::8888") is False
def test_public_ipv6_not_blocked_with_flags(self):
"""Public IPv6 remains unblocked regardless of flags."""
assert (
is_ip_blocked("2001:4860:4860::8888", allow_localhost=True) is False
)
assert (
is_ip_blocked("2001:4860:4860::8888", allow_private_ips=True)
is False
)
# ---------------------------------------------------------------------------
# is_ip_blocked -- localhost / loopback
# ---------------------------------------------------------------------------
class TestIsIpBlockedLoopback:
"""Loopback addresses blocked by default, allowed via flags."""
def test_127_0_0_1_blocked_by_default(self):
"""Standard IPv4 loopback is blocked."""
assert is_ip_blocked("127.0.0.1") is True
def test_127_0_0_2_blocked_by_default(self):
"""Alternate IPv4 loopback address in 127/8 is blocked."""
assert is_ip_blocked("127.0.0.2") is True
def test_127_255_255_255_blocked_by_default(self):
"""End of IPv4 loopback range is blocked."""
assert is_ip_blocked("127.255.255.255") is True
def test_ipv6_loopback_blocked_by_default(self):
"""IPv6 loopback ::1 is blocked."""
assert is_ip_blocked("::1") is True
def test_127_0_0_1_allowed_with_allow_localhost(self):
"""allow_localhost=True unblocks 127.0.0.1."""
assert is_ip_blocked("127.0.0.1", allow_localhost=True) is False
def test_127_range_allowed_with_allow_localhost(self):
"""allow_localhost=True unblocks the whole 127/8 range."""
assert is_ip_blocked("127.0.0.2", allow_localhost=True) is False
assert is_ip_blocked("127.255.255.255", allow_localhost=True) is False
def test_ipv6_loopback_allowed_with_allow_localhost(self):
"""allow_localhost=True unblocks ::1."""
assert is_ip_blocked("::1", allow_localhost=True) is False
def test_127_0_0_1_allowed_with_allow_private_ips(self):
"""allow_private_ips=True also unblocks loopback."""
assert is_ip_blocked("127.0.0.1", allow_private_ips=True) is False
def test_ipv6_loopback_allowed_with_allow_private_ips(self):
"""allow_private_ips=True also unblocks IPv6 loopback."""
assert is_ip_blocked("::1", allow_private_ips=True) is False
# ---------------------------------------------------------------------------
# is_ip_blocked -- RFC1918 private ranges
# ---------------------------------------------------------------------------
class TestIsIpBlockedRFC1918:
"""RFC1918 private addresses blocked by default, allowed with allow_private_ips."""
# -- 10.0.0.0/8 --
def test_10_0_0_1_blocked(self):
assert is_ip_blocked("10.0.0.1") is True
def test_10_255_255_255_blocked(self):
assert is_ip_blocked("10.255.255.255") is True
def test_10_x_allowed_with_allow_private_ips(self):
assert is_ip_blocked("10.0.0.1", allow_private_ips=True) is False
assert is_ip_blocked("10.255.255.255", allow_private_ips=True) is False
def test_10_x_still_blocked_with_allow_localhost(self):
"""allow_localhost does NOT unblock RFC1918."""
assert is_ip_blocked("10.0.0.1", allow_localhost=True) is True
# -- 172.16.0.0/12 --
def test_172_16_0_1_blocked(self):
assert is_ip_blocked("172.16.0.1") is True
def test_172_31_255_255_blocked(self):
assert is_ip_blocked("172.31.255.255") is True
def test_172_15_255_255_not_blocked(self):
"""172.15.x.x is outside 172.16.0.0/12 and therefore public."""
assert is_ip_blocked("172.15.255.255") is False
def test_172_32_0_0_not_blocked(self):
"""172.32.x.x is outside 172.16.0.0/12 and therefore public."""
assert is_ip_blocked("172.32.0.0") is False
def test_172_16_x_allowed_with_allow_private_ips(self):
assert is_ip_blocked("172.16.0.1", allow_private_ips=True) is False
assert is_ip_blocked("172.31.255.255", allow_private_ips=True) is False
def test_172_16_x_still_blocked_with_allow_localhost(self):
assert is_ip_blocked("172.16.0.1", allow_localhost=True) is True
# -- 192.168.0.0/16 --
def test_192_168_1_1_blocked(self):
assert is_ip_blocked("192.168.1.1") is True
def test_192_168_0_1_blocked(self):
assert is_ip_blocked("192.168.0.1") is True
def test_192_168_255_255_blocked(self):
assert is_ip_blocked("192.168.255.255") is True
def test_192_168_x_allowed_with_allow_private_ips(self):
assert is_ip_blocked("192.168.1.1", allow_private_ips=True) is False
assert is_ip_blocked("192.168.255.255", allow_private_ips=True) is False
def test_192_168_x_still_blocked_with_allow_localhost(self):
assert is_ip_blocked("192.168.1.1", allow_localhost=True) is True
# ---------------------------------------------------------------------------
# is_ip_blocked -- AWS metadata endpoint (ALWAYS blocked)
# ---------------------------------------------------------------------------
class TestIsIpBlockedAWSMetadata:
"""169.254.169.254 must ALWAYS be blocked -- the #1 SSRF target."""
def test_aws_metadata_blocked_default(self):
assert is_ip_blocked("169.254.169.254") is True
def test_aws_metadata_blocked_with_allow_localhost(self):
assert is_ip_blocked("169.254.169.254", allow_localhost=True) is True
def test_aws_metadata_blocked_with_allow_private_ips(self):
"""Critical: even when all private IPs are allowed, AWS metadata stays blocked."""
assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True
def test_aws_metadata_blocked_with_both_flags(self):
"""Both flags together must not unblock AWS metadata."""
assert (
is_ip_blocked(
"169.254.169.254", allow_localhost=True, allow_private_ips=True
)
is True
)
# ---------------------------------------------------------------------------
# is_ip_blocked -- CGNAT (100.64.0.0/10)
# ---------------------------------------------------------------------------
class TestIsIpBlockedCGNAT:
"""CGNAT range used by Podman/rootless containers."""
def test_cgnat_start_blocked(self):
assert is_ip_blocked("100.64.0.1") is True
def test_cgnat_middle_blocked(self):
assert is_ip_blocked("100.100.100.100") is True
def test_cgnat_end_blocked(self):
assert is_ip_blocked("100.127.255.255") is True
def test_cgnat_allowed_with_allow_private_ips(self):
assert is_ip_blocked("100.64.0.1", allow_private_ips=True) is False
assert is_ip_blocked("100.100.100.100", allow_private_ips=True) is False
assert is_ip_blocked("100.127.255.255", allow_private_ips=True) is False
def test_cgnat_blocked_with_allow_localhost_only(self):
"""allow_localhost does NOT unblock CGNAT."""
assert is_ip_blocked("100.64.0.1", allow_localhost=True) is True
def test_just_outside_cgnat_not_blocked(self):
"""100.128.0.0 is outside the 100.64.0.0/10 range and public."""
assert is_ip_blocked("100.128.0.0") is False
# ---------------------------------------------------------------------------
# is_ip_blocked -- link-local (169.254.0.0/16)
# ---------------------------------------------------------------------------
class TestIsIpBlockedLinkLocal:
"""Link-local addresses (169.254.x.x) blocked by default."""
def test_link_local_blocked(self):
assert is_ip_blocked("169.254.1.1") is True
assert is_ip_blocked("169.254.100.100") is True
def test_link_local_beginning_blocked(self):
assert is_ip_blocked("169.254.0.1") is True
def test_link_local_end_blocked(self):
assert is_ip_blocked("169.254.255.255") is True
def test_non_aws_link_local_allowed_with_allow_private_ips(self):
"""Non-AWS link-local addresses allowed with allow_private_ips."""
assert is_ip_blocked("169.254.1.1", allow_private_ips=True) is False
assert is_ip_blocked("169.254.100.100", allow_private_ips=True) is False
assert is_ip_blocked("169.254.0.1", allow_private_ips=True) is False
def test_aws_metadata_still_blocked_within_link_local(self):
"""AWS metadata is a special case within link-local; always blocked."""
assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True
def test_link_local_blocked_with_allow_localhost_only(self):
"""allow_localhost does NOT unblock link-local."""
assert is_ip_blocked("169.254.1.1", allow_localhost=True) is True
# ---------------------------------------------------------------------------
# is_ip_blocked -- IPv6 private ranges
# ---------------------------------------------------------------------------
class TestIsIpBlockedIPv6Private:
"""IPv6 ULA (fc00::/7) and link-local (fe80::/10) blocked by default."""
# -- Unique Local Addresses (fc00::/7) --
def test_fc00_blocked(self):
assert is_ip_blocked("fc00::1") is True
def test_fd00_blocked(self):
assert is_ip_blocked("fd00::1") is True
def test_fd_range_blocked(self):
"""fd12:3456:789a::1 is in fc00::/7."""
assert is_ip_blocked("fd12:3456:789a::1") is True
def test_fc00_allowed_with_allow_private_ips(self):
assert is_ip_blocked("fc00::1", allow_private_ips=True) is False
def test_fd00_allowed_with_allow_private_ips(self):
assert is_ip_blocked("fd00::1", allow_private_ips=True) is False
def test_fc00_blocked_with_allow_localhost_only(self):
assert is_ip_blocked("fc00::1", allow_localhost=True) is True
# -- Link-Local (fe80::/10) --
def test_fe80_blocked(self):
assert is_ip_blocked("fe80::1") is True
def test_fe80_with_interface_id_blocked(self):
assert is_ip_blocked("fe80::1234:5678") is True
def test_fe80_allowed_with_allow_private_ips(self):
assert is_ip_blocked("fe80::1", allow_private_ips=True) is False
assert is_ip_blocked("fe80::1234:5678", allow_private_ips=True) is False
def test_fe80_blocked_with_allow_localhost_only(self):
assert is_ip_blocked("fe80::1", allow_localhost=True) is True
# ---------------------------------------------------------------------------
# is_ip_blocked -- "this" network (0.0.0.0/8)
# ---------------------------------------------------------------------------
class TestIsIpBlockedZeroNetwork:
"""The 0.0.0.0/8 'this' network is blocked."""
def test_0_0_0_0_blocked(self):
assert is_ip_blocked("0.0.0.0") is True
def test_0_x_x_x_blocked(self):
assert is_ip_blocked("0.1.2.3") is True
def test_0_255_255_255_blocked(self):
assert is_ip_blocked("0.255.255.255") is True
# ---------------------------------------------------------------------------
# is_ip_blocked -- invalid / edge-case inputs
# ---------------------------------------------------------------------------
class TestIsIpBlockedInvalidInputs:
"""Invalid or non-IP inputs must return False (not blocked)."""
def test_invalid_ip_string_returns_false(self):
assert is_ip_blocked("not-an-ip") is False
def test_empty_string_returns_false(self):
assert is_ip_blocked("") is False
def test_hostname_returns_false(self):
assert is_ip_blocked("example.com") is False
def test_url_returns_false(self):
"""A full URL is not a valid IP string."""
assert is_ip_blocked("http://127.0.0.1") is False
def test_ip_with_port_returns_false(self):
"""IP:port is not a valid IP address."""
assert is_ip_blocked("127.0.0.1:8080") is False
def test_ip_with_trailing_space_returns_false(self):
"""Whitespace around an IP makes it invalid."""
assert is_ip_blocked(" 127.0.0.1 ") is False
def test_ip_with_cidr_returns_false(self):
"""CIDR notation is not a single IP address."""
assert is_ip_blocked("10.0.0.0/8") is False
def test_negative_numbers_returns_false(self):
assert is_ip_blocked("-1.0.0.0") is False
def test_oversized_octet_returns_false(self):
assert is_ip_blocked("256.256.256.256") is False
# ---------------------------------------------------------------------------
# is_ip_blocked -- combined flags
# ---------------------------------------------------------------------------
class TestIsIpBlockedCombinedFlags:
"""Test behaviour when multiple flags are provided together."""
def test_both_flags_allows_loopback(self):
assert (
is_ip_blocked(
"127.0.0.1", allow_localhost=True, allow_private_ips=True
)
is False
)
def test_both_flags_allows_private(self):
assert (
is_ip_blocked(
"192.168.1.1", allow_localhost=True, allow_private_ips=True
)
is False
)
def test_both_flags_still_blocks_aws_metadata(self):
assert (
is_ip_blocked(
"169.254.169.254", allow_localhost=True, allow_private_ips=True
)
is True
)
def test_both_flags_does_not_block_public(self):
assert (
is_ip_blocked(
"8.8.8.8", allow_localhost=True, allow_private_ips=True
)
is False
)
# ---------------------------------------------------------------------------
# is_ip_blocked -- boundary IPs
# ---------------------------------------------------------------------------
class TestIsIpBlockedBoundaries:
"""Test IPs at the exact boundaries of blocked ranges."""
def test_first_ip_of_10_range(self):
assert is_ip_blocked("10.0.0.0") is True
def test_last_ip_of_10_range(self):
assert is_ip_blocked("10.255.255.255") is True
def test_just_before_10_range_not_blocked(self):
"""9.255.255.255 is just before 10.0.0.0/8."""
assert is_ip_blocked("9.255.255.255") is False
def test_just_after_10_range_not_blocked(self):
"""11.0.0.0 is just after 10.0.0.0/8."""
assert is_ip_blocked("11.0.0.0") is False
def test_first_ip_of_172_16_range(self):
assert is_ip_blocked("172.16.0.0") is True
def test_last_ip_of_172_16_range(self):
assert is_ip_blocked("172.31.255.255") is True
def test_ip_172_15_255_255_not_blocked(self):
"""Just below the 172.16.0.0/12 range."""
assert is_ip_blocked("172.15.255.255") is False
def test_ip_172_32_0_0_not_blocked(self):
"""Just above the 172.16.0.0/12 range."""
assert is_ip_blocked("172.32.0.0") is False
def test_first_ip_of_192_168_range(self):
assert is_ip_blocked("192.168.0.0") is True
def test_last_ip_of_192_168_range(self):
assert is_ip_blocked("192.168.255.255") is True
def test_ip_192_167_255_255_not_blocked(self):
assert is_ip_blocked("192.167.255.255") is False
def test_ip_192_169_0_0_not_blocked(self):
assert is_ip_blocked("192.169.0.0") is False
def test_first_ip_of_cgnat(self):
assert is_ip_blocked("100.64.0.0") is True
def test_last_ip_of_cgnat(self):
assert is_ip_blocked("100.127.255.255") is True
def test_ip_100_63_255_255_not_blocked(self):
"""Just below CGNAT range."""
assert is_ip_blocked("100.63.255.255") is False
def test_ip_100_128_0_0_not_blocked(self):
"""Just above CGNAT range."""
assert is_ip_blocked("100.128.0.0") is False
# ---------------------------------------------------------------------------
# get_safe_url -- None / empty handling
# ---------------------------------------------------------------------------
class TestGetSafeUrlNoneAndEmpty:
"""get_safe_url returns default for None or empty strings."""
def test_none_returns_default_none(self):
assert get_safe_url(None) is None
def test_none_returns_custom_default(self):
assert (
get_safe_url(None, default="https://fallback.com")
== "https://fallback.com"
)
def test_empty_string_returns_default_none(self):
assert get_safe_url("") is None
def test_empty_string_returns_custom_default(self):
assert (
get_safe_url("", default="https://fallback.com")
== "https://fallback.com"
)
class TestGetSafeUrlPassthrough:
"""get_safe_url passes through valid URLs and blocks unsafe ones."""
def test_non_empty_url_returned(self):
with patch(
"socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("93.184.216.34", 0))],
):
result = get_safe_url("https://example.com")
assert result == "https://example.com"
def test_http_url_returned(self):
with patch(
"socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("93.184.216.34", 0))],
):
result = get_safe_url("http://example.com")
assert result == "http://example.com"
def test_url_with_path_returned(self):
with patch(
"socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("93.184.216.34", 0))],
):
result = get_safe_url("https://example.com/path?q=1")
assert result == "https://example.com/path?q=1"
def test_localhost_url_returns_default(self):
"""validate_url blocks localhost, so get_safe_url returns default."""
result = get_safe_url("http://127.0.0.1:8080", default="safe")
assert result == "safe"
def test_default_not_used_for_valid_url(self):
with patch(
"socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("93.184.216.34", 0))],
):
result = get_safe_url(
"https://example.com", default="https://fallback.com"
)
assert result == "https://example.com"
# ---------------------------------------------------------------------------
# validate_url -- real validation (no test bypass)
# ---------------------------------------------------------------------------
class TestValidateUrlRealValidation:
"""Confirm validate_url performs real SSRF validation."""
def test_blocks_private_ip_url(self):
"""Private IP URL is blocked."""
assert validate_url("http://192.168.1.1") is False
def test_blocks_localhost_url(self):
"""Localhost URL is blocked."""
assert validate_url("http://127.0.0.1") is False
def test_blocks_aws_metadata_url(self):
"""AWS metadata URL is blocked."""
assert validate_url("http://169.254.169.254/latest/meta-data") is False