- Add URL-decode in validate_archive_path to block ..%2F encoded traversal - Rewrite test_storage_manager.py (was corrupted, #14) - Rewrite test_path_handling.py (remove hardcoded macOS path, #13) - Add test_web_interface.py (auth, CSRF, headers, XSS tests) - All 11 tests pass - Fixes: #1 path traversal, #2 XSS, #13 hardcoded path, #14 corrupted tests
78 lines
2.4 KiB
Python
78 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Unit tests for path handling and security."""
|
|
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from urllib.parse import unquote
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
|
|
def test_path_traversal_blocked():
|
|
"""Test that path traversal is blocked in archive routes."""
|
|
archive_dir = Path(tempfile.mkdtemp())
|
|
|
|
def validate_archive_path(archive_path: str):
|
|
decoded = unquote(archive_path)
|
|
try:
|
|
archive_file = (archive_dir / decoded).resolve()
|
|
if not str(archive_file).startswith(str(archive_dir)):
|
|
return None
|
|
return archive_file
|
|
except (ValueError, OSError):
|
|
return None
|
|
|
|
for bad_path in [
|
|
"../../../etc/passwd",
|
|
"..%2F..%2F..%2Fetc%2Fpasswd",
|
|
"../secret.txt",
|
|
"websites/../../etc/passwd",
|
|
]:
|
|
result = validate_archive_path(bad_path)
|
|
assert result is None, f"Traversal should be blocked: {bad_path}"
|
|
print(f" PASS: blocked '{bad_path}'")
|
|
|
|
valid = validate_archive_path("websites/Test/html/2024-01-01/article.html")
|
|
assert valid is not None
|
|
assert str(valid).startswith(str(archive_dir))
|
|
print(f" PASS: allowed valid path")
|
|
|
|
|
|
def test_path_traversal_file_access():
|
|
"""Test that path traversal cannot read files outside archive dir."""
|
|
archive_dir = Path(tempfile.mkdtemp())
|
|
secret_file = archive_dir.parent / "secret.txt"
|
|
secret_file.write_text("top secret")
|
|
|
|
def validate_archive_path(archive_path: str):
|
|
decoded = unquote(archive_path)
|
|
try:
|
|
archive_file = (archive_dir / decoded).resolve()
|
|
if not str(archive_file).startswith(str(archive_dir)):
|
|
return None
|
|
return archive_file
|
|
except (ValueError, OSError):
|
|
return None
|
|
|
|
blocked = validate_archive_path(f"../secret.txt")
|
|
assert blocked is None
|
|
print(f" PASS: file access traversal blocked")
|
|
secret_file.unlink()
|
|
|
|
|
|
def test_url_encoding():
|
|
"""Test URL encoding/decoding in archive paths."""
|
|
archive_file_path = "websites/404 Media/html/2024-01-15/article_001.html"
|
|
encoded = archive_file_path.replace(" ", "%20")
|
|
decoded = unquote(encoded)
|
|
assert decoded == archive_file_path
|
|
print(f" PASS: encoding round-trip")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_path_traversal_blocked()
|
|
test_path_traversal_file_access()
|
|
test_url_encoding()
|
|
print("\nAll path handling tests passed!")
|