From c01f821680267579a559ef681328c6490cdf2107 Mon Sep 17 00:00:00 2001 From: Julius Scheuerer <95489434+JuliusScheuerer@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:12:04 +0100 Subject: [PATCH] Fix CSP blocking inline translations and broken routes.py indentation (#5 followup) The i18n PR (#5) introduced two bugs: (1) broken indentation in _template_response() causing SyntaxError at import, and (2) an inline - + diff --git a/tests/conftest.py b/tests/conftest.py index 2f12f0e..5f8b8ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,30 @@ """Shared test fixtures.""" +from __future__ import annotations + import pytest from fastapi.testclient import TestClient from document_anonymizer.api.app import app +from document_anonymizer.security.rate_limiter import RateLimiterMiddleware + + +def _find_rate_limiter(app_obj: object) -> RateLimiterMiddleware | None: + """Walk the middleware stack to find the RateLimiterMiddleware instance.""" + current: object | None = getattr(app_obj, "middleware_stack", None) + while current is not None: + if isinstance(current, RateLimiterMiddleware): + return current + current = getattr(current, "app", None) + return None + + +@pytest.fixture(autouse=True) +def _reset_rate_limiter() -> None: + """Clear rate limiter state before each test to prevent 429 cascade.""" + limiter = _find_rate_limiter(app) + if limiter is not None: + limiter._requests.clear() @pytest.fixture diff --git a/tests/test_web/test_routes.py b/tests/test_web/test_routes.py index 746b1ae..9164bc9 100644 --- a/tests/test_web/test_routes.py +++ b/tests/test_web/test_routes.py @@ -287,6 +287,36 @@ def test_csp_header_present(self) -> None: assert "frame-ancestors 'none'" in csp assert "font-src 'self'" in csp + def test_csp_nonce_in_script_src(self) -> None: + """CSP script-src must include a per-request nonce.""" + r = client.get("/") + csp = r.headers["Content-Security-Policy"] + assert "'nonce-" in csp + + def test_csp_nonce_differs_per_request(self) -> None: + """Each request must get a unique CSP nonce.""" + r1 = client.get("/") + r2 = client.get("/") + csp1 = r1.headers["Content-Security-Policy"] + csp2 = r2.headers["Content-Security-Policy"] + assert csp1 != csp2 + + def test_csp_nonce_matches_inline_script(self) -> None: + """The nonce in the CSP header must match the nonce on the inline script tag.""" + import re + + r = client.get("/") + csp = r.headers["Content-Security-Policy"] + csp_match = re.search(r"'nonce-([^']+)'", csp) + assert csp_match is not None + csp_nonce = csp_match.group(1) + + html_match = re.search(r'