security: Dashboard-Admin-Routen per Interims-Gate auf Heimnetz begrenzt (Befund o)

server.py (auf dem Pi, nicht im Repo): /api/ban, /api/unban,
/api/toggle-registration, /api/create-invite, /api/run-stats nur noch fuer
Heimnetz-/localhost-Socket-IPs ohne Cloudflare-Header; sonst 403 mit
Erklaerung. Headless verifiziert (9 Pruefungen). Bernds A/B-Entscheidung
fuer den Fernzugriff bleibt offen (docs/DASHBOARD_AUTH_HARDENING.md).
Zusatzbefund: Repo privat schalten wuerde In-App-Updater brechen (ROADMAP).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-04 09:17:56 +02:00
parent e97a748b35
commit 455742d1bf
4 changed files with 129 additions and 29 deletions
+59 -1
View File
@@ -13,6 +13,61 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format:
---
## 2026-07-04 Dashboard-Befund (o): akutes Loch mit aussperr-sicherem Interims-Gate geschlossen
**Erledigt:** Der KRITISCHE Befund (o) (Dashboard-Admin-Endpoints ohne Auth,
öffentlich erreichbar) ist entschärft, OHNE Bernds A/B-Entscheidung
vorwegzunehmen und OHNE ihn auszusperren. Erkenntnis, die das möglich machte:
Öffentlicher Verkehr erreicht `server.py` (Port 8080) ausschließlich über den
Cloudflare-Tunnel-Container (Absender-IP 172.x + `Cf-*`-Header) Bernds
Heimnetz-Zugriffe kommen dagegen direkt mit `192.168.*`-Socket-IP an. Daher
„Weg C" als Interims-Gate in `server.py`: Die 5 Admin-Routen (`/api/ban`,
`/api/unban`, `/api/toggle-registration`, `/api/create-invite`, `/api/run-stats`)
akzeptieren nur noch Anfragen mit Heimnetz-/localhost-Socket-IP (nicht spoofbar)
und ohne Cloudflare-Header; sonst 403 mit deutscher Erklärung inkl.
Heimnetz-URL das Dashboard zeigt `d.error` per `alert()`, Bernd sieht also
genau, was zu tun ist. Abgelehnte Versuche landen größenbegrenzt (5-MB-Deckel,
Lehre aus Befund (l)) in `security_alerts.log` (nur Fixpfad + Socket-IP, kein
attacker-kontrollierter Text → kein HTML-Injection-Risiko in der
Dashboard-Anzeige). Dienst-Neustart per kill/`Restart=always`, headless
verifiziert (9 Prüfungen): öffentlich ban/toggle → 403, localhost UND
192.168.178.71 → 400 „Kein Nutzer angegeben" (Gate passiert, App-Validierung
erreicht), gespoofter `Cf-Connecting-Ip` aus dem LAN → 403, `GET /` öffentlich
200, `/api/livekit-token` 401 und `/api/e2ee-diagnostics` 403 wie bisher
(bleiben bewusst öffentlich, die App braucht sie). Test-Einträge aus dem
Alert-Log wieder entfernt. Rückbau jederzeit: `server.py.bak-20260704-dashgate`.
Doku/ROADMAP nachgezogen (`docs/DASHBOARD_AUTH_HARDENING.md`: Status, Weg C,
Restrisiken).
**Zusätzlicher Befund (für Bernds Secret-Rotation wichtig):** Das Gitea-Repo
einfach privat zu schalten ist KEINE schnelle Lösung für den Secret-Leak (k):
Der In-App-Updater (`lib/core/update_checker.dart`) holt Releases **anonym**
über die öffentliche Gitea-API Repo privat = Utas installierte App findet
keine Updates mehr, und ein Fix ließe sich nicht mehr per Update verteilen
(Henne-Ei). In ROADMAP M0 beim Rotations-Punkt als Warnung vermerkt.
**Offen/Nächster Schritt:** Bernds Entscheidung bleibt für den FERNZUGRIFF
relevant (Weg A empfohlen schließt auch die weiterhin öffentliche
Nutzerlisten-/Präsenz-Ansicht; Restrisiken im Doc: LAN-CSRF, offene Ansicht).
Danach ggf. Gate ergänzen/ersetzen. Sonst unverändert: übrige M0-Punkte
brauchen PC/Gerät oder Bernd (SQLCipher, Rotation, LiveKit-Client-Umstellung,
Härtetest).
**Stolperfallen:** (1) `stats.html` wird alle 5 Minuten von `stats.sh` (Cron)
NEU GENERIERT Änderungen an der HTML-Datei sind flüchtig, UI-Änderungen
müssten in `stats.sh` (deshalb bewusst NUR server-seitig gefixt). (2) Auf dem
Pi laufen ZWEI cloudflared-Instanzen: die lokal konfigurierte
(`cloudflared.yml`, nur Jellyfin) und eine remote-verwaltete
(`cloudflared-pingvin`, Ingress liegt im Cloudflare-Dashboard, lokal nicht
einsehbar) Letztere trägt `dashboard.`/`git.steggi-matrix.work`. Wer nur die
lokale YAML liest, hält die Subdomains fälschlich für tot. (3) Der
Header-Check allein würde nicht reichen (Header sind von Direktverbindern
spoofbar/wegzulassen) die Socket-IP ist die belastbare Wahrheit, der
Cf-Header-Check ist nur die zweite Schranke, falls der Tunnel je im
Host-Netz läuft.
---
## 2026-07-04 Fable-5-Review (n)+(o): LiveKit-Route live+geprüft, KRITISCHER Auth-Befund am Dashboard
**Erledigt:** Review-Pass über das, was seit (l)/(m) am Server dazugekommen ist
@@ -573,7 +628,10 @@ schließt den Review ab.
Königsweg, kein Code; B: eigener `DASH_TOKEN` + `stats.html`-Prompt) liegt in
`docs/DASHBOARD_AUTH_HARDENING.md`. **Braucht Bernds Wahl A oder B, dann
sofortige Umsetzung + headless-Verifikation.** In ROADMAP M0 als dringender
Punkt aufgenommen.
Punkt aufgenommen. **Update (später am 2026-07-04):** Das akute Loch ist mit
einem aussperr-sicheren Interims-Gate geschlossen (Admin-Routen nur noch
Heimnetz/localhost, siehe PROGRESS-Eintrag „Dashboard-Befund (o)" oben)
offen bleibt nur noch die Fernzugriffs-Entscheidung A/B.
**Fazit:** Die Sonnet-5-Arbeit war handwerklich sauber; die zwei echten Lücken
(verlorene Kommentare, fehlender Soft-Logout-Guard) hatte die abgebrochene