- 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
72 lines
2.4 KiB
Python
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!")
|