"""Tests für app.report — insbesondere XSS/XXE-Escape-Verhalten. Abdeckung der Bug-Klasse aus Issue #57 (Security Audit, Befunde #2 und #6): das LLM-Output landet direkt in der HTML-Vorlage, die von WeasyPrint gerendert wird. Ohne Escape kann eine Prompt-Injection mit einem rohen ```` Local File Read im Container auslösen oder `` nach") assert "**") assert "" in out assert "" _XXE = '' def _build_xss_assessment() -> Assessment: return _minimal_assessment( title=f"Antrag {_XSS}", gwoe_begruendung=f"Begründung mit {_XSS}", gwoe_schwerpunkt=[f"Schwerpunkt {_XXE}"], antrag_zusammenfassung=f"Zusammenfassung mit {_XXE}", antrag_kernpunkte=[f"Kernpunkt mit {_XSS}"], staerken=[f"Stärke mit {_XSS}"], schwaechen=[f"Schwäche mit {_XXE}"], gwoe_matrix=[ MatrixEntry(field="A1", label="Lieferant:innen × 1", aspect=f"Aspekt {_XSS}", rating=2), ], wahlprogramm_scores=[ FraktionScores( fraktion=f"Fraktion {_XSS}", wahlprogramm=ProgrammScore(score=5.0, begruendung=f"WP {_XXE}", zitate=[]), parteiprogramm=ProgrammScore(score=5.0, begruendung=f"PP {_XSS}", zitate=[]), ), ], verbesserungen=[ Verbesserung( original=f"Original {_XSS}", vorschlag=f"Vorschlag **mit {_XSS} markup**", begruendung=f"Begründung {_XXE}", ), ], ) def test_generate_html_report_escapes_all_llm_payloads(tmp_path: Path): a = _build_xss_assessment() out = tmp_path / "report.html" asyncio.run(generate_html_report(a, out, bundesland="NRW")) html = out.read_text() # Negative: keine rohen Angriffs-Strings assert "