Export-Metadaten: category_labels JSON-robust parsen, Keyword-Sanitizer

- category_labels ist in der DB ein JSON-Dict (primary/secondary/tertiary/
  mentioned), nicht ein Komma-String. Der bisherige split(",") fuehrte dazu,
  dass ein nacktes { als Keyword durchrutschte. WeasyPrint bricht den
  PDF-Keywords-Stream an dieser Stelle ab, weil { in PDF-Syntax eine
  Sonderbedeutung hat — Ergebnis war "OSINT, Live-Monitoring, AegisSight, {".
- Neuer Parser: erst JSON (Dict oder Liste), Fallback auf Komma-String.
- _sanitize_keyword(): filtert {, }, [, ], Backslash und normalisiert
  Whitespace in allen Keywords (Defense in Depth).
Dieser Commit ist enthalten in:
claude-dev
2026-04-20 19:09:38 +00:00
Ursprung c0f68e40a5
Commit 9293e66d01

Datei anzeigen

@@ -451,24 +451,49 @@ def _build_export_metadata(
if organization_name: if organization_name:
keywords.append(organization_name) keywords.append(organization_name)
# category_labels ist ein Komma-getrennter String # category_labels: kann JSON-Dict (Karte primary/secondary/...), JSON-Liste
cat_labels = incident.get("category_labels") or "" # oder ein Komma-getrennter String sein. Nur die Label-Werte extrahieren.
for lbl in cat_labels.split(","): cat_labels_raw = (incident.get("category_labels") or "").strip()
lbl = lbl.strip() if cat_labels_raw:
if lbl: cat_values: list[str] = []
keywords.append(lbl) try:
parsed = json.loads(cat_labels_raw)
if isinstance(parsed, dict):
cat_values = [str(v).strip() for v in parsed.values() if isinstance(v, str) and v.strip()]
elif isinstance(parsed, list):
cat_values = [str(v).strip() for v in parsed if isinstance(v, str) and v.strip()]
except (json.JSONDecodeError, TypeError):
cat_values = [lbl.strip() for lbl in cat_labels_raw.split(",") if lbl.strip()]
# Keine JSON-Fragmente (geschweifte/eckige Klammern) als Keyword zulassen
for lbl in cat_values:
if lbl and not any(c in lbl for c in "{}[]"):
keywords.append(lbl)
if top_locations: if top_locations:
keywords.extend([loc for loc in top_locations if loc]) keywords.extend([loc for loc in top_locations if loc])
# Sanitize: Zeilenumbrueche/Tabs weg, Sonderzeichen mit PDF-Sonderbedeutung filtern
def _sanitize_keyword(kw: str) -> str:
if not kw:
return ""
# Whitespace normalisieren
cleaned = re.sub(r"\s+", " ", kw).strip()
# PDF-Dict/Array-Klammern und Backslash raus (WeasyPrint escaped () bei Strings,
# { und [ koennen aber den Keywords-Stream abschneiden)
cleaned = re.sub(r"[{}\[\]\\]", "", cleaned)
return cleaned.strip(" ,;:")
# Dedup (case-insensitive) mit Reihenfolge erhalten, max 15 # Dedup (case-insensitive) mit Reihenfolge erhalten, max 15
seen = set() seen = set()
unique_keywords: list[str] = [] unique_keywords: list[str] = []
for kw in keywords: for kw in keywords:
key = kw.lower() clean_kw = _sanitize_keyword(kw)
if not clean_kw:
continue
key = clean_kw.lower()
if key not in seen: if key not in seen:
seen.add(key) seen.add(key)
unique_keywords.append(kw) unique_keywords.append(clean_kw)
if len(unique_keywords) >= 15: if len(unique_keywords) >= 15:
break break