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

371 lines
13 KiB
Python

"""
Behavioral tests for ssrf_validator module.
Tests the SSRF (Server-Side Request Forgery) validation functions.
"""
import pytest
class TestIsIPBlockedLoopback:
"""Tests for loopback address blocking."""
def test_127_0_0_1_blocked_by_default(self):
"""127.0.0.1 is blocked by default."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("127.0.0.1") is True
def test_127_0_0_1_allowed_with_localhost_flag(self):
"""127.0.0.1 is allowed with allow_localhost=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("127.0.0.1", allow_localhost=True) is False
def test_loopback_range_blocked(self):
"""Entire 127.x.x.x range is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("127.255.255.255") is True
def test_ipv6_loopback_blocked(self):
"""IPv6 loopback ::1 is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("::1") is True
def test_ipv6_loopback_allowed_with_flag(self):
"""IPv6 loopback allowed with allow_localhost=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("::1", allow_localhost=True) is False
class TestIsIPBlockedPrivate:
"""Tests for private IP address blocking."""
def test_10_x_blocked(self):
"""10.x.x.x is blocked by default."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("10.0.0.1") is True
def test_172_16_x_blocked(self):
"""172.16.x.x is blocked by default."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("172.16.0.1") is True
def test_192_168_x_blocked(self):
"""192.168.x.x is blocked by default."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("192.168.1.1") is True
def test_private_allowed_with_flag(self):
"""Private IPs allowed with allow_private_ips=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("10.0.0.1", allow_private_ips=True) is False
assert is_ip_blocked("172.16.0.1", allow_private_ips=True) is False
assert is_ip_blocked("192.168.1.1", allow_private_ips=True) is False
class TestIsIPBlockedCGNAT:
"""Tests for CGNAT (100.64.0.0/10) address handling."""
def test_cgnat_blocked_by_default(self):
"""CGNAT range is blocked by default."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("100.64.0.1") is True
def test_cgnat_allowed_with_private_flag(self):
"""CGNAT allowed with allow_private_ips=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("100.64.0.1", allow_private_ips=True) is False
def test_cgnat_end_of_range(self):
"""End of CGNAT range is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("100.127.255.255") is True
class TestIsIPBlockedLinkLocal:
"""Tests for link-local address handling."""
def test_link_local_blocked(self):
"""Link-local 169.254.x.x is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("169.254.0.1") is True
def test_link_local_allowed_with_private_flag(self):
"""Link-local allowed with allow_private_ips=True (except AWS)."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
# Regular link-local should be allowed
assert is_ip_blocked("169.254.1.1", allow_private_ips=True) is False
class TestIsIPBlockedAWSMetadata:
"""Tests for AWS metadata endpoint blocking."""
def test_aws_metadata_always_blocked(self):
"""AWS metadata endpoint 169.254.169.254 is always blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
# Should be blocked even with all flags
assert is_ip_blocked("169.254.169.254") is True
assert is_ip_blocked("169.254.169.254", allow_localhost=True) is True
assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True
class TestIsIPBlockedPublic:
"""Tests for public IP address handling."""
def test_public_ip_not_blocked(self):
"""Public IPs are not blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("8.8.8.8") is False
assert is_ip_blocked("1.1.1.1") is False
assert is_ip_blocked("208.67.222.222") is False
class TestIsIPBlockedIPv6Private:
"""Tests for IPv6 private address handling."""
def test_ipv6_fc00_blocked(self):
"""IPv6 fc00::/7 unique local is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("fc00::1") is True
def test_ipv6_fd00_blocked(self):
"""IPv6 fd00:: unique local is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("fd00::1") is True
def test_ipv6_fe80_blocked(self):
"""IPv6 fe80:: link-local is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("fe80::1") is True
def test_ipv6_private_allowed_with_flag(self):
"""IPv6 private allowed with allow_private_ips=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("fc00::1", allow_private_ips=True) is False
assert is_ip_blocked("fe80::1", allow_private_ips=True) is False
class TestIsIPBlockedInvalid:
"""Tests for invalid IP address handling."""
def test_invalid_ip_not_blocked(self):
"""Invalid IP returns False (not blocked)."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("not-an-ip") is False
def test_empty_string_not_blocked(self):
"""Empty string returns False."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("") is False
def test_hostname_not_blocked(self):
"""Hostname is not blocked (not an IP)."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("example.com") is False
class TestIsIPBlockedZeroNetwork:
"""Tests for 0.0.0.0/8 network handling."""
def test_0_0_0_0_blocked(self):
"""0.0.0.0 is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("0.0.0.0") is True
def test_0_x_x_x_blocked(self):
"""0.x.x.x network is blocked."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("0.1.2.3") is True
class TestPrivateIpRangesBehavior:
"""Verify is_ip_blocked rejects an interior address from every entry of
PRIVATE_IP_RANGES.
Replaces the prior TestBlockedIPRanges class, which asserted
``ip_network("127.0.0.0/8") in BLOCKED_IP_RANGES`` — a tautology that
re-stated the constant rather than exercising blocking behavior. The
parametrized assertions here would fail if any range entry were
accidentally removed from PRIVATE_IP_RANGES in ip_ranges.py.
"""
@pytest.mark.parametrize(
"ip,range_label",
[
("127.0.0.5", "127.0.0.0/8 IPv4 loopback"),
("::1", "::1/128 IPv6 loopback"),
("10.0.0.5", "10.0.0.0/8 RFC1918 class A"),
("172.16.0.5", "172.16.0.0/12 RFC1918 class B"),
("172.31.255.254", "172.16.0.0/12 RFC1918 class B upper bound"),
("192.168.0.5", "192.168.0.0/16 RFC1918 class C"),
("100.64.0.5", "100.64.0.0/10 CGNAT"),
("169.254.0.5", "169.254.0.0/16 link-local"),
("fe80::1", "fe80::/10 IPv6 link-local"),
("fc00::1", "fc00::/7 IPv6 ULA"),
("0.0.0.5", "0.0.0.0/8 unspecified IPv4"),
("::", "::/128 unspecified IPv6"),
("2002:7f00:1::", "2002::/16 6to4 wrapping loopback"),
("64:ff9b::a9fe:a9fe", "64:ff9b::/96 NAT64 well-known"),
("64:ff9b:1::a9fe:a9fe", "64:ff9b:1::/48 NAT64 local-use"),
("2001::1", "2001::/32 Teredo"),
("100::1", "100::/64 discard"),
("::7f00:1", "::/96 IPv4-compatible IPv6 wrapping loopback"),
],
)
def test_interior_address_blocked(self, ip, range_label):
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked(ip) is True, (
f"Expected {ip} to be blocked because it falls in {range_label}"
)
class TestAllowedSchemes:
"""Tests for ALLOWED_SCHEMES constant."""
def test_allowed_schemes_is_set(self):
"""ALLOWED_SCHEMES is a set."""
from local_deep_research.security.ssrf_validator import ALLOWED_SCHEMES
assert isinstance(ALLOWED_SCHEMES, set)
def test_http_in_allowed(self):
"""http is in ALLOWED_SCHEMES."""
from local_deep_research.security.ssrf_validator import ALLOWED_SCHEMES
assert "http" in ALLOWED_SCHEMES
def test_https_in_allowed(self):
"""https is in ALLOWED_SCHEMES."""
from local_deep_research.security.ssrf_validator import ALLOWED_SCHEMES
assert "https" in ALLOWED_SCHEMES
class TestAlwaysBlockedMetadataIPs:
"""Tests for ALWAYS_BLOCKED_METADATA_IPS constant."""
def test_aws_imds_in_always_blocked(self):
"""AWS / Azure / OCI / DigitalOcean shared IMDS IP is in the set."""
from local_deep_research.security.ssrf_validator import (
ALWAYS_BLOCKED_METADATA_IPS,
)
assert "169.254.169.254" in ALWAYS_BLOCKED_METADATA_IPS
class TestGetSafeUrl:
"""Tests for get_safe_url function."""
def test_returns_url_if_safe(self):
"""Returns URL if it's safe."""
from unittest.mock import patch
from local_deep_research.security.ssrf_validator import get_safe_url
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_returns_none_for_empty(self):
"""Returns default for empty URL."""
from local_deep_research.security.ssrf_validator import get_safe_url
result = get_safe_url("")
assert result is None
def test_returns_default_for_empty(self):
"""Returns specified default for empty URL."""
from local_deep_research.security.ssrf_validator import get_safe_url
result = get_safe_url("", default="https://fallback.com")
assert result == "https://fallback.com"
def test_returns_none_for_none_input(self):
"""Returns default for None input."""
from local_deep_research.security.ssrf_validator import get_safe_url
result = get_safe_url(None)
assert result is None
class TestValidateUrlNoBypass:
"""Tests that validate_url performs real validation (no test bypass)."""
def test_localhost_blocked_by_default(self):
"""validate_url blocks localhost by default."""
from local_deep_research.security.ssrf_validator import validate_url
result = validate_url("http://127.0.0.1")
assert result is False
def test_testing_env_does_not_bypass(self, monkeypatch):
"""TESTING=true environment variable does NOT bypass validation."""
from local_deep_research.security.ssrf_validator import validate_url
monkeypatch.setenv("TESTING", "true")
result = validate_url("http://127.0.0.1")
assert result is False
class TestAllowLocalhostFlag:
"""Tests for allow_localhost flag interaction."""
def test_localhost_not_blocked_with_flag(self):
"""Localhost is not blocked when allow_localhost=True."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("127.0.0.1", allow_localhost=True) is False
assert is_ip_blocked("::1", allow_localhost=True) is False
def test_private_still_blocked_with_localhost_flag(self):
"""Private IPs still blocked with allow_localhost (not allow_private)."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
# allow_localhost only allows loopback, not all private
assert is_ip_blocked("10.0.0.1", allow_localhost=True) is True
assert is_ip_blocked("192.168.1.1", allow_localhost=True) is True
class TestAllowPrivateIPsFlag:
"""Tests for allow_private_ips flag interaction."""
def test_private_ips_includes_loopback(self):
"""allow_private_ips also allows loopback."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("127.0.0.1", allow_private_ips=True) is False
def test_aws_metadata_still_blocked_with_private(self):
"""AWS metadata is still blocked even with allow_private_ips."""
from local_deep_research.security.ssrf_validator import is_ip_blocked
assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True