From 9e2d843e199031d62a7742eb4c91f3d66f79aabc Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Wed, 3 Jun 2026 23:52:04 +0000 Subject: [PATCH] Upload tests/test_frontend_security.py --- tests/test_frontend_security.py | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 tests/test_frontend_security.py diff --git a/tests/test_frontend_security.py b/tests/test_frontend_security.py new file mode 100644 index 0000000..2532a7b --- /dev/null +++ b/tests/test_frontend_security.py @@ -0,0 +1,177 @@ +"""Frontend-Security Tests: XSS-mitigation, JWT-handling, CSP-middleware. + +These tests enforce the rules from architecture Section 13 + task graph R-5: + + - R-5: No `x-html` attribute anywhere in any HTML template (only x-text). + - R-1: JWT must be read from / written to localStorage (not cookies, not sessionStorage). + - 13.3: CSP-Header is configured in app/main.py (security_headers_middleware), + not in a reverse-proxy config file. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent +WEBUI = ROOT / "app" / "webui" +MAIN_PY = ROOT / "app" / "main.py" + + +# --------------------------------------------------------------------------- +# R-5: no x-html in any HTML template +# --------------------------------------------------------------------------- + +# Match `x-html` as a whole word (attribute, not part of `x-htmlSomething`). +# We deliberately allow `x-html="..."` to fail loudly, and we also catch +# any camel-cased variant like `xHtml` for paranoia. +XHTML_PATTERN = re.compile(r"\bx[-_]?html\b", re.IGNORECASE) + + +def _all_html_files() -> list[Path]: + return sorted(WEBUI.glob("*.html")) + + +def test_no_x_html_in_alpine_templates(): + """R-5: no `x-html` attribute anywhere in any HTML file.""" + offenders: list[tuple[str, int, str]] = [] + for path in _all_html_files(): + for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + # Strip HTML comments to avoid false positives in documentation + stripped = re.sub(r"", "", line) + if XHTML_PATTERN.search(stripped): + offenders.append((path.name, lineno, line.strip()[:120])) + assert not offenders, ( + "x-html usage is forbidden (R-5); offenders:\n" + + "\n".join(f" {n}:{ln}: {snippet}" for n, ln, snippet in offenders) + ) + + +def test_no_innerHTML_in_alpine_pages(): + """Defence in depth: also flag direct `innerHTML` assignments.""" + for path in _all_html_files(): + content = path.read_text(encoding="utf-8") + # allow within