"""CSRF-Schutz – leichtgewichtig, ohne Flask-WTF (siehe CLAUDE.md: „so wenig wie möglich"). Ein Session-gebundenes Token wird in jedes POST-Formular geschrieben und bei jeder unsicheren Anfrage geprüft. Reicht für eine interne App mit Session-Cookies. Nutzung im Template: """ import hmac import secrets from flask import abort, render_template, request, session CSRF_SESSION_KEY = "_csrf_token" CSRF_FORM_FIELD = "csrf_token" CSRF_HEADER = "X-CSRFToken" SAFE_METHODS = {"GET", "HEAD", "OPTIONS", "TRACE"} def generate_csrf(): """Token der aktuellen Session zurückgeben (beim ersten Aufruf erzeugen).""" if CSRF_SESSION_KEY not in session: session[CSRF_SESSION_KEY] = secrets.token_urlsafe(32) return session[CSRF_SESSION_KEY] def _validate_csrf(): erwartet = session.get(CSRF_SESSION_KEY) gesendet = request.form.get(CSRF_FORM_FIELD) or request.headers.get(CSRF_HEADER) # compare_digest: konstante Laufzeit, verträgt aber keine None-Werte. if not erwartet or not gesendet or not hmac.compare_digest(erwartet, gesendet): abort(400, description="csrf") def init_csrf(app): @app.before_request def csrf_protect(): if request.method not in SAFE_METHODS: _validate_csrf() @app.errorhandler(400) def csrf_fehler(err): # Nur die CSRF-Fälle bekommen die freundliche Seite; andere 400 unverändert. if getattr(err, "description", None) == "csrf": return render_template("400_csrf.html"), 400 return err # Im Template als csrf_token() verfügbar machen. app.jinja_env.globals["csrf_token"] = generate_csrf