NewsArchiverV2/tests/test_web_interface.py
Jarian Cottingham 7565366138 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-07-05 04:06:06 +00:00

72 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Unit tests for web_interface.py security features."""
import subprocess
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
sys.path.insert(0, str(Path(__file__).parent.parent))
def test_validate_archive_path_blocks_traversal():
"""Test validate_archive_path function blocks traversal."""
from web_interface import validate_archive_path, ARCHIVE_DIR
for bad in ["../../../etc/passwd", "..%2Fetc%2Fpasswd", "../secret.txt"]:
result = validate_archive_path(bad)
assert result is None, f"Should block: {bad}"
print(" PASS: traversal blocked")
def test_xss_safe_filter_removed():
"""Test that article.html no longer uses |safe filter."""
result = subprocess.run(
["grep", "-rn", "|safe", "templates/article.html"],
capture_output=True, text=True,
)
assert result.returncode != 0, "article.html should not contain |safe"
print(" PASS: no |safe in article.html")
def test_security_headers_present():
"""Test security headers are added to responses."""
from web_interface import app
client = app.test_client()
resp = client.get("/")
assert resp.headers.get("X-Content-Type-Options") == "nosniff"
assert resp.headers.get("X-Frame-Options") == "DENY"
assert resp.headers.get("X-XSS-Protection") == "1; mode=block"
assert resp.headers.get("Referrer-Policy") == "strict-origin-when-cross-origin"
print(" PASS: security headers present")
def test_server_url_not_hardcoded():
"""Test that SERVER_URL comes from env, not hardcoded."""
result = subprocess.run(
["grep", "-n", "192\\.168", "web_interface.py"],
capture_output=True, text=True,
)
assert result.returncode != 0, "No hardcoded internal IPs"
print(" PASS: no hardcoded IPs")
def test_no_safe_filter_in_rss():
"""Test RSS/Atom feeds escape content properly."""
result = subprocess.run(
["grep", "-rn", "|safe", "templates/rss.xml", "templates/atom.xml"],
capture_output=True, text=True,
)
assert result.returncode != 0, "No |safe in RSS templates"
print(" PASS: no |safe in RSS/Atom")
if __name__ == "__main__":
test_validate_archive_path_blocks_traversal()
test_xss_safe_filter_removed()
test_security_headers_present()
test_server_url_not_hardcoded()
test_no_safe_filter_in_rss()
print("\nAll web interface tests passed!")