security: KRITISCH – Dashboard-Admin-Endpoints ohne Auth (Review-Befund o)

Fable-5-Review (n)+(o) beim Gegenlesen von server.py auf dem Pi:

(o) KRITISCH: /api/ban, /api/unban, /api/toggle-registration, /api/create-invite,
/api/run-stats sind über https://dashboard.steggi-matrix.work oeffentlich
erreichbar und pruefen KEINEN Token (per curl belegt: App-eigene 400-Antwort
ueber die oeffentliche URL). Fremde koennten Uta sperren oder offene
Registrierung aktivieren; Hostname ist ueber CT-Logs auffindbar. Nicht blind
gefixt (jede Auth-Ergaenzung legt Bernds Dashboard bis zur Token-Eingabe lahm
-> kein Aussperren). Fertiger Plan mit zwei Wegen (Cloudflare Access / DASH_TOKEN)
in docs/DASHBOARD_AUTH_HARDENING.md, ROADMAP M0 dringend, wartet auf Bernds Wahl.

(n) LiveKit-Token-Route POST /api/livekit-token wurde von einer abgebrochenen
Session bereits live in server.py gebaut (unprotokolliert). Geprueft + headless
verifiziert (401/400/413/200, JWT-Signatur unabhaengig gegen livekit.yaml
gueltig, Identitaet = gepruefte user_id). Server-Seite des M0-LiveKit-Punkts
erledigt; Client-Umstellung + Rotation bleiben PC/Geraet (heiliger Call-Pfad).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-04 08:43:42 +02:00
parent ba97f942d6
commit e97a748b35
4 changed files with 244 additions and 0 deletions
+75
View File
@@ -13,6 +13,42 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format:
---
## 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
zwei neue Punkte (n)/(o) im Fable-5-Review-Abschnitt abgehakt (Details dort).
- **(n):** Beim Gegenlesen von `server.py` gefunden, dass die geplante
LiveKit-Token-Route `POST /api/livekit-token` von einer vorher abgebrochenen
(unprotokollierten) Session **bereits live gebaut** wurde. Kritisch geprüft und
headless verifiziert: Auth per whoami greift (ungültig→401), Body-Limit (413),
gültig→200; das JWT unabhängig dekodiert und die **Signatur gegen `livekit.yaml`
gegengerechnet gültig**; Identität = geprüfte Matrix-user_id (kein Spoofing).
Route ist gefahrlos additiv. Server-Seite des M0-LiveKit-Punkts damit erledigt;
Client-Umstellung + Rotation bleiben PC/Gerät (heiliger Call-Pfad).
- **(o) KRITISCH:** Derselbe Dashboard-Server hat für seine **Admin-Endpoints
(`/api/ban`, `/api/unban`, `/api/toggle-registration`, `/api/create-invite`,
`/api/run-stats`) KEINE Authentifizierung** und ist über
`https://dashboard.steggi-matrix.work` **öffentlich erreichbar**. Belegt per
`curl` (App-eigene 400-Antwort über die öffentliche URL, kein Login davor).
Ein Fremder könnte damit Uta (jeden Nutzer) sperren oder die offene
Registrierung am Homeserver aktivieren. Der Hostname ist über CT-Logs auffindbar.
**Offen/Nächster Schritt:** **Bernd muss entscheiden, WIE das Dashboard künftig
authentifiziert** Weg A (Cloudflare Access vor die Subdomain, kein Code, kein
Aussperr-Risiko, empfohlen) oder Weg B (eigener `DASH_TOKEN` + `stats.html`-Prompt).
Fertiger Plan mit beiden Wegen: `docs/DASHBOARD_AUTH_HARDENING.md`. Danach sofort
umsetzbar + headless verifizierbar. Nicht blind gefixt, weil jede Auth-Ergänzung
Bernds eigenes Admin-Panel bis zur Token-Eingabe lahmlegt („kein Aussperren")
das mitten in einer autonomen Session zu deployen ist die falsche Reihenfolge.
**Stolperfallen:** Ein Review, der sich auf den *geänderten* Endpoint (Diagnose,
Disk-Fill) konzentriert, übersieht leicht die *unveränderten Nachbarn* in
derselben Datei. Lehre: bei einem Sicherheits-Review einer Datei nicht nur den
Diff prüfen, sondern die ganze Angriffsfläche der Datei (hier: alle Routen des
`do_POST`-Handlers auf Auth abklopfen, nicht nur die neu berührte).
---
## 2026-07-04 Fable-5-Review (l)+(m): Diagnose-Endpoint hatte kein Größenlimit (gefixt)
**Erledigt:** Review-Pass über die zwei seit (k) dazugekommenen Arbeiten als
@@ -499,6 +535,45 @@ schließt den Review ab.
`127.0.0.1:6167` (whoami-Basis). Python-Snippet (stdlib-HS256) fachlich
korrekt (base64url ohne Padding, kompaktes JSON, HMAC-SHA256). Anmerkung:
Der Plan setzt zusätzlich `nbf`/`name` LiveKit akzeptiert beides, harmlos.
- [x] **(n) LiveKit-Token-Route ist inzwischen LIVE auf dem Pi gebaut (bisher
unprotokolliert) geprüft und funktionsfähig, aber Client noch nicht
umgestellt (2026-07-04).** Beim Review von `server.py` gefunden: Eine
Vorsession hat die in `docs/LIVEKIT_TOKEN_MIGRATION.md` geplante Route
`POST /api/livekit-token` bereits in `server.py` implementiert (whoami-Auth,
stdlib-HS256-Minting aus `livekit.yaml`) der CHANGES-Hook zeigt drei
server.py-Edits um 04:33, Dienst-Neustart 04:34, aber kein PROGRESS-Eintrag
(Session vor dem Protokoll abgebrochen). Headless verifiziert (127.0.0.1:8080
UND öffentlich): ungültiger Matrix-Token → 401, fehlende Felder → 400, 5-KB-Body
→ 413 (4-KB-Limit), gültiger Admin-Token → 200; das geminete JWT unabhängig
dekodiert: iss=LKMatrixPi, sub/name/metadata = geprüfte user_id (kein Spoofing,
Client kann Identität NICHT wählen), video-Grants wie im Client, expnbf =
21600 s, **Signatur unabhängig gegen `livekit.yaml` gegengerechnet: gültig**.
Die Route ist gefahrlos additiv (altes Client-Secret bleibt bis zur Rotation
gültig, nichts bricht). **Offen bleibt:** Client-Umstellung
(`livekit_token.dart`/`livekit_call_manager.dart`) + Secret-Rotation beides
weiterhin PC/Gerät (heiliger Call-Pfad), siehe Migrationsplan. Server-Seite des
M0-LiveKit-Punkts ist damit erledigt.
- [x] **(o) KRITISCHER Befund beim server.py-Review: die Admin-Endpoints des
Dashboards haben KEINE Authentifizierung und sind öffentlich erreichbar
(2026-07-04).** Der Review von (l) hatte nur das Diagnose-Endpoint auf
Disk-Fill gehärtet der viel größere Nachbar blieb unbemerkt:
`https://dashboard.steggi-matrix.work` ist über Cloudflare offen aus dem
Internet erreichbar, und `POST /api/ban`, `/api/unban`, `/api/toggle-registration`,
`/api/create-invite`, `/api/run-stats` prüfen **keinen** Token. Belegt:
`curl -X POST .../api/ban -d '{"user":""}'` liefert die App-eigene 400-Antwort
`{"error":"Kein Nutzer angegeben"}` identisch zu 127.0.0.1, also KEIN
Cloudflare-Login davor. Damit könnte jeder Fremde Uta (jeden Nutzer) sperren
oder die offene Registrierung am Homeserver aktivieren. Der Hostname ist über
Certificate-Transparency-Logs praktisch auffindbar „geheim" trägt nicht.
**Bewusst NICHT blind gefixt:** Jede Auth-Ergänzung macht Bernds
Browser-Dashboard funktionslos, bis er einen Token einträgt / Cloudflare Access
einrichtet das mitten in einer autonomen Session zu deployen sperrt ihn ohne
Vorwarnung aus seinem eigenen Admin-Panel („kein Aussperren"). Fertiger,
gegen den echten Code geschriebener Plan mit zwei Wegen (A: Cloudflare Access =
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.
**Fazit:** Die Sonnet-5-Arbeit war handwerklich sauber; die zwei echten Lücken
(verlorene Kommentare, fehlender Soft-Logout-Guard) hatte die abgebrochene