test_ssrf_guard.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """T-Gap 5: Unit tests for assert_safe_spoolman_url."""
  2. import pytest
  3. from backend.app.api.routes._spoolman_helpers import assert_safe_spoolman_url
  4. class TestSsrfGuardAccepted:
  5. """URLs that must be accepted (normal Spoolman topologies)."""
  6. def test_localhost_http(self):
  7. assert_safe_spoolman_url("http://localhost:7912")
  8. def test_localhost_https(self):
  9. assert_safe_spoolman_url("https://localhost:7912")
  10. def test_loopback_ipv4(self):
  11. assert_safe_spoolman_url("http://127.0.0.1:7912")
  12. def test_rfc1918_192_168(self):
  13. assert_safe_spoolman_url("http://192.168.1.50:7912")
  14. def test_rfc1918_10_x(self):
  15. assert_safe_spoolman_url("http://10.0.0.5:7912")
  16. def test_rfc1918_172_16(self):
  17. assert_safe_spoolman_url("http://172.16.0.1:7912")
  18. def test_hostname_based(self):
  19. assert_safe_spoolman_url("http://spoolman.local:7912")
  20. def test_internal_dns(self):
  21. assert_safe_spoolman_url("http://internal.corp:7912")
  22. def test_https_with_path(self):
  23. assert_safe_spoolman_url("https://spoolman.example.com/api")
  24. class TestSsrfGuardRejected:
  25. """URLs that must be rejected as SSRF-dangerous."""
  26. def test_cloud_metadata_169_254(self):
  27. with pytest.raises(ValueError, match="Spoolman URL"):
  28. assert_safe_spoolman_url("http://169.254.169.254/latest/meta-data/")
  29. def test_alibaba_cloud_metadata(self):
  30. with pytest.raises(ValueError):
  31. assert_safe_spoolman_url("http://100.100.100.200/latest/meta-data/")
  32. def test_multicast_ipv4(self):
  33. with pytest.raises(ValueError):
  34. assert_safe_spoolman_url("http://224.0.0.1:7912")
  35. def test_unspecified_ipv4(self):
  36. with pytest.raises(ValueError):
  37. assert_safe_spoolman_url("http://0.0.0.0:7912")
  38. def test_scheme_file(self):
  39. with pytest.raises(ValueError, match="http or https"):
  40. assert_safe_spoolman_url("file:///etc/passwd")
  41. def test_scheme_gopher(self):
  42. with pytest.raises(ValueError, match="http or https"):
  43. assert_safe_spoolman_url("gopher://spoolman.local:7912")
  44. def test_scheme_dict(self):
  45. with pytest.raises(ValueError, match="http or https"):
  46. assert_safe_spoolman_url("dict://localhost:1234/")
  47. def test_numeric_encoded_decimal(self):
  48. # 2130706433 == 127.0.0.1 in decimal — must be rejected
  49. with pytest.raises(ValueError):
  50. assert_safe_spoolman_url("http://2130706433:7912")
  51. def test_numeric_encoded_hex(self):
  52. # 0x7f000001 == 127.0.0.1 in hex — must be rejected
  53. with pytest.raises(ValueError):
  54. assert_safe_spoolman_url("http://0x7f000001:7912")
  55. def test_ipv4_mapped_ipv6_metadata(self):
  56. # ::ffff:169.254.169.254 — IPv4-mapped IPv6 bypass attempt
  57. with pytest.raises(ValueError):
  58. assert_safe_spoolman_url("http://[::ffff:169.254.169.254]:7912")
  59. def test_multicast_ipv6(self):
  60. with pytest.raises(ValueError):
  61. assert_safe_spoolman_url("http://[ff02::1]:7912")
  62. def test_unspecified_ipv6(self):
  63. with pytest.raises(ValueError):
  64. assert_safe_spoolman_url("http://[::]:7912")
  65. def test_aws_imds_ipv6_blocked(self):
  66. """F10: AWS IMDS IPv6 address fd00:ec2::254 must be blocked."""
  67. with pytest.raises(ValueError):
  68. assert_safe_spoolman_url("http://[fd00:ec2::254]:7912")