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

@ -4,10 +4,12 @@ import os
import shutil
import time
import logging
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from database import db
from auth import get_current_user, get_current_user_optional
from ratelimit import check as rl_check, block_ip
logger = logging.getLogger(__name__)
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
@ -17,6 +19,17 @@ SUBMIT_DIR = os.path.join(BREEDS_DIR, "submissions")
router = APIRouter()
# ------------------------------------------------------------------
# Honeypot — URL die kein echter Browser aufruft
# GET /api/wiki/trap → IP 24h sperren
# ------------------------------------------------------------------
@router.get("/trap", include_in_schema=False)
async def honeypot(request: Request):
block_ip(request, hours=24)
logger.warning("Honeypot ausgelöst von %s", request.client.host if request.client else "?")
raise HTTPException(404, "Not found")
# ------------------------------------------------------------------
# Schemas
# ------------------------------------------------------------------
@ -82,11 +95,13 @@ async def get_stats():
# ------------------------------------------------------------------
@router.get("/rassen")
async def get_rassen(
request: Request,
search: str = Query(""),
gruppe: str = Query(""),
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
):
rl_check(request, max_requests=60, window_seconds=60, key="wiki_list")
conditions = []
args = []
@ -131,7 +146,8 @@ async def get_rassen(
# GET /api/wiki/rassen/{slug} — Rasse-Detail + Community-Berichte
# ------------------------------------------------------------------
@router.get("/rassen/{rasse_slug}")
async def get_rasse(rasse_slug: str):
async def get_rasse(rasse_slug: str, request: Request):
rl_check(request, max_requests=30, window_seconds=60, key="wiki_detail")
with db() as conn:
rasse = conn.execute(
"SELECT * FROM wiki_rassen WHERE slug = ?", (rasse_slug,)