Commit Graph

4 Commits

Author SHA1 Message Date
Dotty Dotter
64cbff5286 Security hotfixes #1, #2, #6 from audit (#57)
Drei akute Befunde aus dem Live-System-Audit (Issue #57):

- **#1 HIGH** — Resource Exhaustion via öffentlichem POST: slowapi
  Limiter (in-memory, IP-key) auf /analyze (10/min), /api/analyze-drucksache
  (10/min) und /api/programme/index (3/min). Verhindert, dass ein
  unauthentifizierter Client mit einer Schleife die DashScope-Quota oder
  die CPU des Containers leerziehen kann. Default-Storage reicht solange
  wir auf einem einzigen Worker laufen.

- **#2 MEDIUM** + **#6 MEDIUM** (selber Root-Cause) — XXE/Local-File-Read
  via WeasyPrint und Stored XSS via Browser-Rendering: alle LLM-getragenen
  Felder in app/report.py laufen jetzt durch html.escape() bevor sie in
  die HTML-Template interpoliert werden. format_redline_html escape-first
  und ersetzt dann die Markdown-Marker durch von uns kontrollierte
  <span>-Tags. build_matrix_html escaped das aspect-Attribut, sodass ein
  nacktes " den title="..."-Wert nicht mehr beenden und einen Event-
  Handler injizieren kann. Toter jinja2-Import in report.py entfernt
  (war never used, blockierte nur den lokalen Test).

- **Tests** — neue tests/test_report.py mit 8 Cases, die direkt die
  Bug-Klasse verifizieren: <script>, file://-img, "-attribut-breakout
  in Title und ein End-to-End-Render mit XSS-Payloads in jedem LLM-Feld.
  Die Marker-Funktionalität (** und ~~) wird mit-getestet, damit der
  Escape-First-Ansatz das nicht versehentlich kaputt macht.

77 alte Unit-Tests + 8 neue → 85 grün.

Rate-Limit-Verifikation per TestClient ist Integration-Scope und folgt
in tests/integration/test_main_security.py als separates Folge-Item.

Refs: #57

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:45:43 +02:00
Dotty Dotter
73a7f76472 Add E2E functional acceptance test suite (#50, #51, #52, #53, #54)
Vier Sub-Issues unter Umbrella #50 — opt-in via 'pytest -m integration',
Default-Suite (77 Unit-Tests) bleibt unberührt.

- Sub-Issue A (#51): test_adapters_live.py — pro aktivem BL Reachability,
  Drucksache-ID-Format, Type-Filter, Datum-/Fraktion-Plausibilität,
  PDF-Link-HEAD-Probe (slow). NI als xfail (Login-Wall).
- Sub-Issue B (#52): test_frontend_xref.py + ground_truth.py — pro BL
  ein manuell kuratiertes Frontend-Sample (Drucksache + Title-Substring +
  Fraktionen + Datum + PDF-URL), gegen das adapter.get_document() gespiegelt
  wird. Fängt Bug-Klasse 14 (Cross-Bundesland-Match).
- Sub-Issue C (#53): test_wahlprogramme_indexed.py — Indexing-Status pro
  aktivem BL aus embeddings.db, PDF-Inhalts-Plausibilität (14 Marker +
  Wahlperioden-Horizont), expliziter Anti-Marker für Bug-Klasse 8
  (CDU-BE 2021 vs 2026 PDF-Tausch durch abgeordnetenwatch).
- Sub-Issue D (#54): test_citations_substring.py — Property-Verification:
  jedes vom LLM zitierte Snippet muss als (whitespace-normalisierter)
  Substring auf der angegebenen PDF-Seite vorhanden sein. Strict-Match
  mit Truncation-Marker-Toleranz, kein Fuzzy. Liest reale Assessments
  aus gwoe-antraege.db. Fängt Bug-Klassen 7/10/17 (Halluzination).

Architektur: separates tests/integration/ Verzeichnis mit eigenem
conftest.py, das die Stubs der Unit-Suite (fitz/bs4/openai/pydantic_settings)
gezielt entfernt und auf echte Module umstellt — mit Fallback-Skip via
pytest.require_module wenn lokale Dev-Maschine die Prod-Deps nicht hat.

206 neue Integration-Tests, 13 Helper-Unit-Tests. 77 Unit-Tests bleiben grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:00:20 +02:00
Dotty Dotter
db5a875d7c Activate Baden-Württemberg via PARLISAdapter (#29, Phase 1)
PARLIS auf parlis.landtag-bw.de läuft technisch auf demselben
eUI-Backend wie LSA-PADOKA und BE-PARDOK, hat aber drei wichtige
Unterschiede, die eine eigene Klasse statt einer PortalaAdapter-
Subklasse rechtfertigen:

1. Body-Schema: minimales lines mit l1/l2/l3/l4 (statt LSA/BE
   2/3/4/10/11/20.x/90.x), serverrecordname=vorgang,
   format=suchergebnis-vorgang-full, sort=SORT01/D SORT02/D SORT03,
   keine parsed/json-Felder. Quelle: dokukratie/scrapers/portala.query.bw.json
   plus HAR-Verifikation gegen die Live-Instanz.

2. Async polling: die initiale SearchAndDisplay-Antwort liefert nur
   search_id mit status=running, KEINE report_id. Erst eine zweite
   SearchAndDisplay-Anfrage mit id=<search_id> (ohne search-Component)
   bekommt nach 1-3 Sekunden die report_id zurück. Reverse-engineered
   aus esearch-ui.main.js requestReportOK() Z. ~1268.

3. Hit-Format: report.tt.html liefert Records als JSON-in-HTML-Comments
   <!--{"WMV33":[...],"EWBV22":[...],...}-->. Komplett anderes Format
   als LSA Perl-Dump oder BE HTML-Cards. Felder:
     - EWBV22: "Drucksache 17/10323"
     - EWBD05: direkter PDF-URL
     - WMV33: Schlagworte (joined by ;)
     - WMV30: Urheber-Kurzform
     - EWBV23: "Antrag <Urheber> <DD.MM.YYYY>"

Smoke-Test (lokal):
  BW q='':       8 hits in 17s, jüngste WP17-Anträge mit Datum + Fraktion
  BW q='Schule': 8 hits, alle wirklich Schul-bezogen (Hochschule, Grundschule,
                 Schwimmunterricht, Lehrerbedarf etc.)
  BW q='Klima':  8 hits, Klimaschutz/CO2/Energieberatung
  get_document(17/10323): roundtrip funktioniert

bundeslaender.py: aktiv=True für BW; Anmerkung erweitert mit
PARLISAdapter-Verweis und drei-Unterschiede-Hinweis für künftige
Wartung. Test test_four_active_bundeslaender umbenannt zu
test_active_bundeslaender_include_phase_1_set, prüft jetzt nur
Subset-Bedingung statt exakter Count, damit Phase-1/2-Erweiterungen
keine Test-Updates brauchen.

Phase 1 (1/3) aus Roadmap-Issue #49.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:38:04 +02:00
Dotty Dotter
f98e64c734 Add pytest suite + fix two regex bugs uncovered by it (#46)
Erste Tests für die Codebase. 77 Tests, 0.08s Laufzeit, decken die
drei Bug-Klassen aus der April-2026-Adapter-Session ab plus haben
schon zwei weitere Bugs in Production-Code aufgedeckt.

## Setup

- requirements-dev.txt mit pytest + pytest-asyncio
- pytest.ini mit asyncio_mode=auto
- tests/conftest.py stubbt fitz/bs4/openai/pydantic_settings, damit
  die Suite ohne den vollen prod-requirements-Satz läuft (pure unit
  tests, kein PDF-Parsing, kein HTTP)

## Tests

- tests/test_parlamente.py (33 Tests)
  * PortalaAdapter._parse_hit_list_cards: doctype/doctype_full
    NameError-Regression aus 1cb030a, plus Title/Drucksache/Fraktion-
    /Datum/PDF-Extraktion gegen ein BE-Card-Fixture
  * PortalaAdapter._parse_hit_list_dump: gegen ein LSA-Perl-Dump-
    Fixture inkl. Hex-Escape-Decoding (\x{fc} → ü)
  * PortalaAdapter._parse_hit_list_html: Auto-Detection zwischen
    Card- und Dump-Format
  * PortalaAdapter._normalize_fraktion: kanonische Fraktion-Codes
    inkl. F.D.P.-mit-Punkten, BÜNDNIS 90, DIE LINKE, BSW
  * ParLDokAdapter._hit_to_drucksache: JSON-Hit → Drucksache
    Mapping inkl. /navpanes-Stripping, MdL-mit-Partei-in-Klammern,
    Landesregierung-Detection
  * ParLDokAdapter._fulltext_id: bundle.js-mirroring (deferred,
    aber dokumentiert)
  * ADAPTERS-Registry-Sanity

- tests/test_embeddings.py (11 Tests)
  * _chunk_source_label: Programm-Name + Seite (Halluzinations-
    Bug-Regression aus 1b5fd96)
  * format_quotes_for_prompt: jeder Chunk muss Programm-Name
    enthalten, strict-citation-Hinweis muss im Output sein,
    keine NRW-Halluzinationen für MV/BE-Chunk-Sets

- tests/test_wahlprogramme.py (14 Tests)
  * Registry-Struktur (jahr int, seiten int, .pdf-Endung)
  * File-Existenz: jede registrierte PDF muss in
    static/referenzen/ liegen — würde Tippfehler in den 22
    indexierten Programmen sofort fangen
  * embeddings.PROGRAMME-Konsistenz-Cross-Check

- tests/test_bundeslaender.py (15 Tests)
  * Sanity über 16-State-Registry
  * #48-Klassifikations-Regression: TH=ParlDok, HB=StarWeb,
    SN=Eigensystem
  * Wahltermine plausibel (zwischen 2026 und 2035)

- tests/test_analyzer.py (4 Tests)
  * Markdown-Codeblock-Stripping aus dem JSON-Retry-Loop

## Bug-Funde während der Test-Schreibphase

Zwei Production-Bugs in den _normalize_fraktion-Helfern wurden
durch die neuen Tests sofort aufgedeckt und im selben Commit gefixt:

1. PortalaAdapter._normalize_fraktion matched "F.D.P." (mit Punkten,
   wie historische SH/HB-Drucksachen) nicht — Regex \bFDP\b ist zu
   strikt. Fix: \bF\.?\s*D\.?\s*P\.?\b analog zu ParLDokAdapter.

2. ParLDokAdapter._normalize_fraktion (auch PortalaAdapter) matched
   "Ministerium der Finanzen" nicht als Landesregierung, weil
   \bMINISTER\b die Wortgrenze auch nach MINISTER verlangt — bei
   MINISTERIUM steht aber IUM danach, keine Wortgrenze. Fix:
   \bMINISTER ohne abschließendes \b.

Beide Bugs hätten Fraktion-Felder bei Drucksachen der Bremischen
Bürgerschaft (FDP-Listen) und bei Landesregierungs-Drucksachen
in MV/LSA fälschlich leer gelassen — exakt der "fraktionen=[]"-
Befund aus dem MV-Smoke-Test in #4.

Phase 0 aus Roadmap-Issue #49.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:26:06 +02:00