security: server.py Admin-Routen serialisieren (ADMIN_LOCK, Review-Befund q)

Threading-Umstellung aus (p) hatte eine Nebenlaeufigkeits-Race in den
Admin-Routen erzeugt: conduit.toml Read-Modify-Write + docker restart und
der send_admin-Readback-nach-sleep waren seit multithreaded race-faehig.
ADMIN_LOCK serialisiert NUR die Admin-Mutationen; die oeffentlichen Routen
(livekit-token, e2ee-diagnostics) laufen zuerst und bleiben nebenlaeufig,
der Slow-Loris-Schutz aus (p) bleibt erhalten. Headless verifiziert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-04 14:20:42 +02:00
parent d42beaf7ab
commit c8ff850c98
3 changed files with 70 additions and 0 deletions
+4
View File
@@ -1261,3 +1261,7 @@
2026-07-04 13:51 [Edit] /home/steggi/pyramid/PROGRESS.md 2026-07-04 13:51 [Edit] /home/steggi/pyramid/PROGRESS.md
2026-07-04 13:51 [Edit] /home/steggi/pyramid/ROADMAP.md 2026-07-04 13:51 [Edit] /home/steggi/pyramid/ROADMAP.md
2026-07-04 13:51 [Edit] /home/steggi/.claude-pyramid/projects/-home-steggi-pyramid/memory/pyramid-arbeitsstand.md 2026-07-04 13:51 [Edit] /home/steggi/.claude-pyramid/projects/-home-steggi-pyramid/memory/pyramid-arbeitsstand.md
2026-07-04 14:17 [Write] /home/steggi/matrix/server.py
2026-07-04 14:19 [Edit] /home/steggi/pyramid/PROGRESS.md
2026-07-04 14:20 [Edit] /home/steggi/pyramid/PROGRESS.md
2026-07-04 14:20 [Edit] /home/steggi/pyramid/ROADMAP.md
+61
View File
@@ -13,6 +13,53 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format:
--- ---
## 2026-07-04 Fable-5-Review (q): Threading-Härtung (p) tiefer geprüft echte Nebenläufigkeits-Race in den Admin-Routen gefunden und mit ADMIN_LOCK behoben
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit die Threading-Umstellung
aus (p) (Commit 343545b, `ThreadingHTTPServer`) noch einmal kritisch gegengelesen und
als Punkt (q) im Fable-5-Review-Abschnitt abgehakt. Befund: Die Umstellung auf
multithreaded hat einen **echten Race erzeugt, den es vorher (single-threaded, alles
serialisiert) nicht geben konnte** (p) hat nur den öffentlich beschreibbaren
Diagnose-Pfad per `DIAG_LOCK` abgesichert, die **Admin-Routen aber übersehen**:
- `toggle-registration` und `create-invite` machen ein Read-Modify-Write auf
`conduit.toml` **plus** `docker compose restart conduit` zwei gleichzeitige Klicks
= verlorenes Update (lost update) + doppelter Container-Neustart.
- `send_admin()` schläft 4 s und liest danach die **letzten** Nachrichten aus dem
Admin-Raum zurück, um „die" Antwort zuzuordnen zwei gleichzeitige Admin-Befehle
können sich vertauschte Antworten einfangen.
**Fix (`server.py`):** Neues `ADMIN_LOCK` (threading.Lock), das **nur** die
Admin-Mutationen serialisiert. Dafür `do_POST` minimal umgebaut: die beiden
öffentlichen Routen (`/api/livekit-token`, `/api/e2ee-diagnostics`) werden jetzt
**zuerst** behandelt und returnen, danach folgt das Heimnetz-Gate und der komplette
Admin-Dispatch in einem `with ADMIN_LOCK:`. Damit bleiben die öffentlichen Pfade
**nebenläufig** der Slow-Loris-Schutz aus (p) ist unangetastet. Handler-Logik
wortwörtlich unverändert (nur verschoben/eingerückt, per `diff` gegen Backup
verifiziert). `python3 -m py_compile` sauber; Backup `server.py.bak-20260704-adminlock`.
**Verifiziert (headless, nach Dienst-Neustart per kill/`Restart=always`):**
8 Routen-Checks identisch wie vorher (stats 200; ban localhost → 400 „Kein Nutzer",
ban „aus dem Internet"/gespoofter Cf-Header → 403; livekit-token 401/400/413;
e2ee-diagnostics falscher Token 403; unbekannte Route 404). **Race-Beweis:** ein
`run-stats` allein 1388 ms, **zwei parallel 2633 ms** (≈ 2× → serialisiert; ohne
Lock wären es ~1400 ms). **Non-Blocking-Beweis:** ein öffentliches `livekit-token`
**während** ein Admin-`run-stats` den Lock hält → in **11 ms** beantwortet (nicht
blockiert). Test-Deny-Zeile aus `security_alerts.log` wieder entfernt.
**Offen/Nächster Schritt:** Unverändert alle offenen ROADMAP-Punkte brauchen
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
**Stolperfallen:** Eine Threading-Umstellung ist nie „nur" ein Server-Konstruktor-
Tausch sie macht **jeden** geteilten Zustand (Dateien, Read-Modify-Write,
Readback-nach-sleep) plötzlich race-fähig. (p) hatte richtig den einen öffentlich
beschreibbaren Pfad (`DIAG_LOCK`) gesehen, aber die Admin-Routen mit `conduit.toml`
und dem Admin-Raum-Readback sind derselbe Fall. Lehre: Nach „Server ist jetzt
multithreaded" ALLE schreibenden/zustandsändernden Handler durchgehen, nicht nur
den einen, der den Anlass gab.
---
## 2026-07-04 Fable-5-Review (p): Interims-Gate geprüft (korrekt) + halbfertige Threading-Härtung einer abgebrochenen Session vollendet ## 2026-07-04 Fable-5-Review (p): Interims-Gate geprüft (korrekt) + halbfertige Threading-Härtung einer abgebrochenen Session vollendet
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit (Interims-Gate, **Erledigt:** Review-Pass über die jüngste erledigte Arbeit (Interims-Gate,
@@ -684,6 +731,20 @@ schließt den Review ab.
Vollendet (Threading + Lock-Nutzung), Dienst neu gestartet, 11 Headless- Vollendet (Threading + Lock-Nutzung), Dienst neu gestartet, 11 Headless-
Prüfungen grün inkl. Slow-Loris (parallele Anfrage 10 ms statt blockiert; Prüfungen grün inkl. Slow-Loris (parallele Anfrage 10 ms statt blockiert;
hängender Body-Read nach 30 s gekappt). Details im PROGRESS-Eintrag „(p)" oben. hängender Body-Read nach 30 s gekappt). Details im PROGRESS-Eintrag „(p)" oben.
- [x] **(q) Threading-Umstellung aus (p) tiefer geprüft echte Nebenläufigkeits-Race
in den Admin-Routen gefunden und mit `ADMIN_LOCK` behoben (2026-07-04).** (p) hatte
den öffentlich beschreibbaren Diagnose-Pfad per `DIAG_LOCK` abgesichert, aber die
Admin-Routen übersehen: `toggle-registration`/`create-invite` machen ein
Read-Modify-Write auf `conduit.toml` + `docker restart`, und `send_admin()` liest
nach 4 s die letzten Admin-Raum-Nachrichten zurück beides seit multithreaded
race-fähig (lost update, doppelter Neustart, vertauschte Antworten). Fix: neues
`ADMIN_LOCK`, das **nur** die Admin-Mutationen serialisiert; die öffentlichen Routen
(`livekit-token`, `e2ee-diagnostics`) laufen zuerst und bleiben nebenläufig
(Slow-Loris-Schutz aus (p) unangetastet). Handler-Logik wortwörtlich unverändert
(per `diff` gegen Backup verifiziert). Headless verifiziert: 8 Routen-Checks wie
vorher; Race-Beweis (2 parallele `run-stats` 2633 ms ≈ 2× einzeln 1388 ms →
serialisiert); Non-Blocking-Beweis (öffentliches `livekit-token` während gehaltenem
Lock in 11 ms). Details im PROGRESS-Eintrag „(q)" oben.
**Fazit:** Die Sonnet-5-Arbeit war handwerklich sauber; die zwei echten Lücken **Fazit:** Die Sonnet-5-Arbeit war handwerklich sauber; die zwei echten Lücken
(verlorene Kommentare, fehlender Soft-Logout-Guard) hatte die abgebrochene (verlorene Kommentare, fehlender Soft-Logout-Guard) hatte die abgebrochene
+5
View File
@@ -74,6 +74,11 @@ Punkte abhaken (`[x]`), wenn erledigt UND in `PROGRESS.md` protokolliert.
Zusatz (Fable-5-Review (p), 2026-07-04): `server.py` läuft jetzt multithreaded Zusatz (Fable-5-Review (p), 2026-07-04): `server.py` läuft jetzt multithreaded
mit 30-s-Socket-Timeout ein einzelner langsamer Client kann den Server mit 30-s-Socket-Timeout ein einzelner langsamer Client kann den Server
(und damit `/api/livekit-token`) nicht mehr blockieren; headless verifiziert. (und damit `/api/livekit-token`) nicht mehr blockieren; headless verifiziert.
Zusatz (Fable-5-Review (q), 2026-07-04): Der durch (p) eingeführte
Nebenläufigkeits-Race in den Admin-Routen (Read-Modify-Write auf `conduit.toml`,
`send_admin`-Readback) ist mit `ADMIN_LOCK` geschlossen, das NUR die
Admin-Mutationen serialisiert die öffentlichen Routen bleiben nebenläufig;
headless verifiziert (2× parallel = 2× Laufzeit, öffentlicher Pfad 11 ms).
- [x] **LiveKit-Token-Route server-seitig gebaut + verifiziert** (Fable-5-Review (n), - [x] **LiveKit-Token-Route server-seitig gebaut + verifiziert** (Fable-5-Review (n),
PROGRESS.md 2026-07-04). `POST /api/livekit-token` läuft live in `server.py` PROGRESS.md 2026-07-04). `POST /api/livekit-token` läuft live in `server.py`
(whoami-Auth, stdlib-HS256-Minting aus `livekit.yaml`), headless geprüft (whoami-Auth, stdlib-HS256-Minting aus `livekit.yaml`), headless geprüft