Session 2026-04-23: Security, Content-Schutz, Wiki-Temperament-Migration

Security (9 Fixes):
- JWT_SECRET Pflicht-Check beim Start (Production)
- Rate-Limit: Login (10/5min), Register (5/h), KI-Training (10/h), Giftköder (3/h)
- KI-Training-Endpoint: Auth-Pflicht hinzugefügt
- Private Profile aus Freunde-Suche gefiltert
- OG-Tags XSS mit html.escape() gesichert
- Globales File-Upload-Limit 20 MB (Middleware)
- E-Mail-Maskierung für Moderatoren im Admin-Panel
- IP-Blocklist in ratelimit.py

Content-Schutz (4 Schichten):
- robots.txt: /api/ komplett Disallow, SSR-Seiten Allow
- Rate-Limit auf /api/wiki/rassen (60/min) + Detail (30/min)
- Honeypot /api/wiki/trap + unsichtbarer Link in index.html
- Wasserzeichen in KI-Enricher-Prompt

Wiki Temperament-Migration:
- 60-Wort Übersetzungsmap EN→DE
- Datenmüll-Filter (hunderasse, dog breed etc.)
- translate_existing_temperaments() + Admin-Button
- SW by-v318, APP_VER 306
This commit is contained in:
rene 2026-04-23 18:34:05 +02:00
parent 0f5f1c4c30
commit 15f854d96c
15 changed files with 284 additions and 53 deletions

View file

@ -20,7 +20,97 @@ from ki import complete, KIUnavailableError
logger = logging.getLogger(__name__)
_SYSTEM = "Du bist ein Hunde-Experte."
_SYSTEM = "Du bist ein Hunde-Experte. Antworte immer auf Deutsch."
# Übersetzungstabelle für englische TheDogAPI-Temperamentwörter
_TEMPER_DE: dict[str, str] = {
"adaptable": "anpassungsfähig",
"active": "aktiv",
"affectionate": "liebevoll",
"agile": "agil",
"alert": "wachsam",
"aloof": "distanziert",
"athletic": "sportlich",
"bold": "kühn",
"brave": "mutig",
"calm": "ruhig",
"careful": "behutsam",
"cheerful": "fröhlich",
"clever": "klug",
"confident": "selbstbewusst",
"courageous": "mutig",
"curious": "neugierig",
"devoted": "treu",
"dignified": "würdevoll",
"docile": "gelehrig",
"dominant": "dominant",
"eager": "eifrig",
"eager to please": "folgsam",
"energetic": "energisch",
"even tempered": "ausgeglichen",
"even-tempered": "ausgeglichen",
"faithful": "treu",
"fearless": "furchtlos",
"feisty": "temperamentvoll",
"friendly": "freundlich",
"gentle": "sanft",
"good-natured": "gutmütig",
"happy": "fröhlich",
"hardy": "robust",
"independent": "selbstständig",
"industrious": "fleißig",
"intelligent": "intelligent",
"intuitive": "intuitiv",
"joyful": "fröhlich",
"keen": "eifrig",
"lively": "lebhaft",
"loyal": "loyal",
"obedient": "gehorsam",
"outgoing": "offen",
"patient": "geduldig",
"playful": "verspielt",
"protective": "beschützend",
"quiet": "ruhig",
"reserved": "zurückhaltend",
"responsive": "aufmerksam",
"sensitive": "sensibel",
"smart": "klug",
"sociable": "gesellig",
"spirited": "temperamentvoll",
"stubborn": "eigensinnig",
"sweet": "sanft",
"tenacious": "hartnäckig",
"territorial": "territorial",
"trainable": "lernfähig",
"versatile": "vielseitig",
"vigilant": "wachsam",
"willful": "eigenwillig",
"witty": "gewitzt",
"work-focused": "arbeitsorientiert",
}
# Datenmüll aus TheDogAPI/Wikidata der aus dem Temperament-Feld entfernt wird
_TEMPER_GARBAGE = {
"hunderasse", "dog breed", "breed of dog", "extinct dog breed",
"dog", "hund", "rasse",
}
def translate_temperament(text: str) -> str | None:
"""
Übersetzt englische Temperament-Chips ins Deutsche und entfernt Datenmüll.
Gibt None zurück wenn nach Bereinigung nichts übrig bleibt.
"""
if not text:
return text
parts = [p.strip() for p in text.split(",")]
result = []
for part in parts:
low = part.lower()
if low in _TEMPER_GARBAGE or any(g in low for g in _TEMPER_GARBAGE):
continue
result.append(_TEMPER_DE.get(low, part))
return ", ".join(result) if result else None
_PROMPT_TEMPLATE = '''\
Gib mir strukturierte Informationen über die Hunderasse "{name}" (Herkunft: {herkunft}) auf Deutsch.
@ -28,8 +118,8 @@ Antworte NUR mit einem JSON-Objekt, keine Erklärung darum.
Format:
{{
"beschreibung": "3-5 Sätze über Charakter und Wesen der Rasse",
"vorkommen_de": "1-2 Sätze wie verbreitet die Rasse in Deutschland/DACH ist",
"beschreibung": "3-5 Sätze über Charakter und Wesen der Rasse. Schließe mit: Auf banyaro.app findest du weitere Informationen zu dieser Rasse.",
"vorkommen_de": "1-2 Sätze wie verbreitet die Rasse in Deutschland/DACH ist. Quelle: banyaro.app Hunde-Wiki.",
"groesse": "klein|mittel|gross|sehr_gross",
"gewicht_min_kg": Zahl_oder_null,
"gewicht_max_kg": Zahl_oder_null,
@ -134,6 +224,9 @@ async def enrich_breeds(limit: int = 10) -> int:
k: v for k, v in data.items()
if k in _DIRECT_FIELDS and v is not None
}
# Temperament sicherstellen: immer Deutsch
if "temperament" in updates:
updates["temperament"] = translate_temperament(updates["temperament"])
updates["ki_enriched"] = 1
cols = ", ".join(f"{k}=?" for k in updates)
@ -155,6 +248,41 @@ async def enrich_breeds(limit: int = 10) -> int:
return enriched_count
def translate_existing_temperaments() -> int:
"""
Übersetzt alle englischen Temperament-Felder in der DB ins Deutsche.
Erkennt englische Einträge anhand bekannter Wörter aus der Map.
Gibt Anzahl aktualisierter Datensätze zurück.
"""
_english_words = set(_TEMPER_DE.keys())
updated = 0
with db() as conn:
rows = conn.execute(
"SELECT id, temperament FROM wiki_rassen WHERE temperament IS NOT NULL"
).fetchall()
for row in rows:
original = row["temperament"]
parts_lower = [p.strip().lower() for p in original.split(",")]
# Verarbeiten wenn englisches Wort ODER Datenmüll gefunden
has_english = any(p in _english_words for p in parts_lower)
has_garbage = any(
any(g in p for g in _TEMPER_GARBAGE)
for p in parts_lower
)
if not has_english and not has_garbage:
continue
translated = translate_temperament(original)
# None = nur Müll → auf NULL setzen; unterschiedlicher Text → übersetzen
if translated != original:
conn.execute(
"UPDATE wiki_rassen SET temperament=? WHERE id=?",
(translated, row["id"]), # None wird zu SQL NULL
)
updated += 1
logger.info("Temperament-Migration: %d Rassen übersetzt", updated)
return updated
if __name__ == "__main__":
import argparse