basyx commited on
Commit
bd8e89e
·
verified ·
1 Parent(s): d3b4950

Test SSRF and upload filename hardening

Browse files
Files changed (1) hide show
  1. tests/test_downloader.py +54 -0
tests/test_downloader.py CHANGED
@@ -1,8 +1,13 @@
 
 
 
1
  from unittest.mock import AsyncMock
2
 
 
3
  import respx
4
  from httpx import Response
5
 
 
6
  from app.services.downloader import Downloader
7
  from app.services.validator import MediaValidator
8
 
@@ -18,3 +23,52 @@ async def test_url_download_streams_to_disk(settings, tmp_path) -> None:
18
  path, mime_type = await downloader.download(url, tmp_path)
19
  assert path.read_bytes() == b"ID3data"
20
  assert mime_type == "audio/mpeg"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import socket
4
  from unittest.mock import AsyncMock
5
 
6
+ import pytest
7
  import respx
8
  from httpx import Response
9
 
10
+ from app.core.exceptions import InputError
11
  from app.services.downloader import Downloader
12
  from app.services.validator import MediaValidator
13
 
 
23
  path, mime_type = await downloader.download(url, tmp_path)
24
  assert path.read_bytes() == b"ID3data"
25
  assert mime_type == "audio/mpeg"
26
+
27
+
28
+ async def test_validate_url_rejects_non_global_dns_answer(settings, monkeypatch) -> None:
29
+ settings.allow_private_urls = False
30
+ downloader = Downloader(settings, MediaValidator(settings))
31
+
32
+ def private_answer(*args, **kwargs):
33
+ return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("127.0.0.1", 443))]
34
+
35
+ monkeypatch.setattr(socket, "getaddrinfo", private_answer)
36
+ with pytest.raises(InputError, match="Private, loopback"):
37
+ await downloader.validate_url("https://example.test/media.mp4")
38
+
39
+
40
+ async def test_validate_url_rejects_mixed_public_and_private_dns(settings, monkeypatch) -> None:
41
+ settings.allow_private_urls = False
42
+ downloader = Downloader(settings, MediaValidator(settings))
43
+
44
+ def mixed_answers(*args, **kwargs):
45
+ return [
46
+ (socket.AF_INET, socket.SOCK_STREAM, 6, "", ("93.184.216.34", 443)),
47
+ (socket.AF_INET, socket.SOCK_STREAM, 6, "", ("10.0.0.4", 443)),
48
+ ]
49
+
50
+ monkeypatch.setattr(socket, "getaddrinfo", mixed_answers)
51
+ with pytest.raises(InputError, match="Private, loopback"):
52
+ await downloader.validate_url("https://example.test/media.mp4")
53
+
54
+
55
+ @pytest.mark.parametrize("url", [
56
+ "file:///etc/passwd",
57
+ "https://user:password@example.test/media.mp4",
58
+ "https://example.test:invalid/media.mp4",
59
+ "https://example.test:99999/media.mp4",
60
+ ])
61
+ async def test_validate_url_rejects_invalid_or_credentialed_urls(settings, url: str) -> None:
62
+ settings.allow_private_urls = False
63
+ downloader = Downloader(settings, MediaValidator(settings))
64
+ with pytest.raises(InputError):
65
+ await downloader.validate_url(url)
66
+
67
+
68
+ def test_safe_filename_normalizes_windows_paths_and_rejects_header_injection(settings) -> None:
69
+ validator = MediaValidator(settings)
70
+ assert validator.safe_filename(r"C:\\temp\\clip.mp4") == "clip.mp4"
71
+ with pytest.raises(InputError):
72
+ validator.safe_filename("clip.mp4\r\nX-Evil: yes")
73
+ with pytest.raises(InputError):
74
+ validator.safe_filename('clip".mp4')