"""SSRF protection. The important case is a public hostname that resolves to a loopback address. Checking the URL string alone would let it through. """ from __future__ import annotations import socket import pytest from app.config import Settings from app.errors import BlockedTargetError from app.services.security import UrlGuard @pytest.fixture def guard() -> UrlGuard: return UrlGuard(Settings()) def test_literal_loopback_is_blocked(guard: UrlGuard) -> None: with pytest.raises(BlockedTargetError): guard.check("http://127.0.0.1:8000/dokument.html") def test_private_range_is_blocked(guard: UrlGuard) -> None: with pytest.raises(BlockedTargetError): guard.check("http://192.168.1.10/dokument.html") def test_link_local_metadata_endpoint_is_blocked(guard: UrlGuard) -> None: with pytest.raises(BlockedTargetError): guard.check("http://169.254.169.254/latest/meta-data/") def test_hostname_resolving_to_loopback_is_blocked(guard: UrlGuard, monkeypatch) -> None: def fake_getaddrinfo(host, *args, **kwargs): # noqa: ARG001 return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("127.0.0.1", 80))] monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo) with pytest.raises(BlockedTargetError): guard.check("http://vlastni-domena.example.com/dokument.html") def test_file_scheme_is_blocked(guard: UrlGuard) -> None: with pytest.raises(BlockedTargetError): guard.check("file:///etc/passwd") def test_public_address_passes(guard: UrlGuard, monkeypatch) -> None: def fake_getaddrinfo(host, *args, **kwargs): # noqa: ARG001 return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("93.184.216.34", 80))] monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo) assert guard.check("https://example.com/dokument.html") == "example.com" def test_allowlisted_host_skips_the_check() -> None: settings = Settings() object.__setattr__(settings, "ssrf_allowed_hosts", ["localhost"]) guard = UrlGuard(settings) assert guard.check("http://localhost:9000/dokument.html") == "localhost"