NewsArchiverV2/tests/test_path_handling.py
Jarian Cottingham e5292bada7 fix: tests + URL-encoded path traversal fix + fix test files
- 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
2026-08-21 15:45:00 +00:00

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!")