gwoe-antragspruefer/app/validators.py

48 lines
1.6 KiB
Python
Raw Normal View History

Phase A: Audit-Restbefunde #57.3/4/7 (Roadmap #59) Drei verbleibende Audit-Befunde aus #57 in einem Patch: - **#57.3 MEDIUM** Drucksache-Regex-Validation: neue app/validators.py mit validate_drucksache() als gemeinsamer Validation-Funnel. Pattern ^\d{1,3}/\d{1,7}([-(].{1,20})?$ deckt alle 10 aktiven Bundesländer (8/6390, 18/12345, 8/6390(neu), 23/3700-A) ab und blockt Path-Traversal (../, /etc/passwd) plus Standard-Injection (;, <, &). Drei Endpoints durchgeschleust: /api/assessment, /api/assessment/pdf, /api/analyze-drucksache. - **#57.4 MEDIUM** print() → logging.getLogger(__name__): main.py und analyzer.py auf strukturiertes Logging umgestellt. LLM-Inhalte werden NICHT mehr als Volltext geloggt — neue Helper _content_fingerprint() liefert nur "len=N sha1=XXXX", reicht zur Forensik ohne Antrag-Inhalte ins Container-Log zu leaken. basicConfig() mit ISO-Format setzt strukturiertes Logging früh, damit logger.exception() auch beim Boot greift. - **#57.7 LOW-MED** Search-Query-Limit: validate_search_query() mit MAX_SEARCH_QUERY_LEN=200 schützt /api/search und /api/search-landtag vor 10-MB-Query-DoS. database._parse_search_query() loggt jetzt shlex.ValueError-Fallback statt ihn zu verschlucken (deckt Memory- Regel "stille excepts in Adaptern" ab). Tests: neue tests/test_main_validators.py mit 22 Cases — Drucksache- Whitelist-Roundtrip + Path-Traversal-Reject, Search-Query Längen- Edge-Cases. 107 Unit-Tests grün (85 alt + 22 neu). Validators in eigenem Modul (app/validators.py), damit Tests sie ohne slowapi-Dependency direkt importieren können. Refs: #57, #59 (Phase A) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:15:16 +02:00
"""Input-Validators für die FastAPI-Endpoints.
Eigenes Modul, damit die Unit-Tests die Helper direkt importieren können
ohne den vollen FastAPI-/slowapi-Stack aus app.main mitzuziehen. Issue
#57 Befunde #3 (Path-Traversal via drucksache) und #7 (Search-Query-DoS).
"""
from __future__ import annotations
import re
from fastapi import HTTPException
# Drucksache-Format: erlaubt sind alle bisher beobachteten Schreibweisen:
# "8/6390", "18/12345", "8/6390(neu)", "23/3700-A", "21/754S" (HB: S=Stadtbürgerschaft).
# Restriktiv genug für Path-Traversal-Schutz (#57 Befund #3).
_DRUCKSACHE_RE = re.compile(r"^\d{1,3}/\d{1,7}[A-Z]?([-(].{1,20})?$")
Phase A: Audit-Restbefunde #57.3/4/7 (Roadmap #59) Drei verbleibende Audit-Befunde aus #57 in einem Patch: - **#57.3 MEDIUM** Drucksache-Regex-Validation: neue app/validators.py mit validate_drucksache() als gemeinsamer Validation-Funnel. Pattern ^\d{1,3}/\d{1,7}([-(].{1,20})?$ deckt alle 10 aktiven Bundesländer (8/6390, 18/12345, 8/6390(neu), 23/3700-A) ab und blockt Path-Traversal (../, /etc/passwd) plus Standard-Injection (;, <, &). Drei Endpoints durchgeschleust: /api/assessment, /api/assessment/pdf, /api/analyze-drucksache. - **#57.4 MEDIUM** print() → logging.getLogger(__name__): main.py und analyzer.py auf strukturiertes Logging umgestellt. LLM-Inhalte werden NICHT mehr als Volltext geloggt — neue Helper _content_fingerprint() liefert nur "len=N sha1=XXXX", reicht zur Forensik ohne Antrag-Inhalte ins Container-Log zu leaken. basicConfig() mit ISO-Format setzt strukturiertes Logging früh, damit logger.exception() auch beim Boot greift. - **#57.7 LOW-MED** Search-Query-Limit: validate_search_query() mit MAX_SEARCH_QUERY_LEN=200 schützt /api/search und /api/search-landtag vor 10-MB-Query-DoS. database._parse_search_query() loggt jetzt shlex.ValueError-Fallback statt ihn zu verschlucken (deckt Memory- Regel "stille excepts in Adaptern" ab). Tests: neue tests/test_main_validators.py mit 22 Cases — Drucksache- Whitelist-Roundtrip + Path-Traversal-Reject, Search-Query Längen- Edge-Cases. 107 Unit-Tests grün (85 alt + 22 neu). Validators in eigenem Modul (app/validators.py), damit Tests sie ohne slowapi-Dependency direkt importieren können. Refs: #57, #59 (Phase A) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:15:16 +02:00
def validate_drucksache(drucksache: str) -> str:
"""Lehnt jede Drucksache-ID ab, die nicht dem erwarteten Format
entspricht. Gemeinsamer Validation-Funnel für alle Endpoints, die
``drucksache`` als Parameter nehmen. Issue #57 Befund #3.
"""
if not drucksache or not _DRUCKSACHE_RE.match(drucksache):
raise HTTPException(
status_code=400,
detail=f"Ungültige Drucksache-ID: {drucksache!r}",
)
return drucksache
# Längen-Limit für freie Search-Queries — verhindert, dass jemand mit einem
# 10-MB-Query die SQL-LIKE-Pattern oder den HTTP-Adapter aushungert.
# Issue #57 Befund #7.
MAX_SEARCH_QUERY_LEN = 200
def validate_search_query(q: str) -> str:
if q is None:
raise HTTPException(status_code=400, detail="Suchbegriff fehlt")
if len(q) > MAX_SEARCH_QUERY_LEN:
raise HTTPException(
status_code=400,
detail=f"Suchbegriff zu lang (max {MAX_SEARCH_QUERY_LEN} Zeichen, war {len(q)})",
)
return q