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

@ -3,11 +3,13 @@ BAN YARO — FastAPI Hauptanwendung
"""
import os
import html
import logging
from collections import deque
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from contextlib import asynccontextmanager
from database import init_db
@ -61,6 +63,22 @@ app = FastAPI(
redoc_url = None,
)
# Globales File-Upload-Limit (20 MB)
_MAX_UPLOAD_BYTES = 20 * 1024 * 1024
class _UploadSizeMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.method in ("POST", "PUT", "PATCH"):
cl = request.headers.get("content-length")
if cl and int(cl) > _MAX_UPLOAD_BYTES:
return JSONResponse(
status_code=413,
content={"detail": f"Datei zu groß (max. {_MAX_UPLOAD_BYTES // 1024 // 1024} MB)."}
)
return await call_next(request)
app.add_middleware(_UploadSizeMiddleware)
# ------------------------------------------------------------------
# API-Router registrieren (werden nach und nach hinzugefügt)
@ -649,24 +667,25 @@ async def public_dog_page(dog_id: int):
except Exception:
pass
html = f"""<!DOCTYPE html>
_s = html.escape # XSS-Schutz für OG-Meta-Tags
_html = f"""<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{_og_name} Ban Yaro</title>
<meta name="description" content="{_og_desc}">
<title>{_s(_og_name)} Ban Yaro</title>
<meta name="description" content="{_s(_og_desc)}">
<meta name="robots" content="noindex">
<meta property="og:type" content="profile">
<meta property="og:title" content="{_og_name} — Ban Yaro">
<meta property="og:description" content="{_og_desc}">
<meta property="og:title" content="{_s(_og_name)} — Ban Yaro">
<meta property="og:description" content="{_s(_og_desc)}">
<meta property="og:url" content="https://banyaro.app/hund/{dog_id}">
<meta property="og:image" content="{_og_img}">
<meta property="og:image" content="{_s(_og_img)}">
<meta property="og:locale" content="de_DE">
<meta property="og:site_name" content="Ban Yaro">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{_og_name} — Ban Yaro">
<meta name="twitter:image" content="{_og_img}">
<meta name="twitter:title" content="{_s(_og_name)} — Ban Yaro">
<meta name="twitter:image" content="{_s(_og_img)}">
<link rel="stylesheet" href="/css/design-system.css">
<link rel="stylesheet" href="/css/components.css">
<style>
@ -947,7 +966,7 @@ async def public_dog_page(dog_id: int):
</body>
</html>"""
from fastapi.responses import HTMLResponse
return HTMLResponse(content=html)
return HTMLResponse(content=_html)
# ------------------------------------------------------------------