fix: Rufnummern bei Testzugang serverseitig maskieren, nicht nur per CSS blurren

CSS-Blur (Sicher-Modus) verdeckt nur die Anzeige - im Seitenquelltext,
DevTools oder Netzwerk-Tab standen die echten Rufnummern trotzdem drin,
weil sie ganz normal im JSON von /api/anrufe und /api/verlauf ankamen.

Bei Testzugang (per Token statt Passwort, self._via_token) werden nummer/
rueckrufnummer jetzt vor dem Senden durch einen Platzhalter ersetzt
(_maskiere_anrufe), im Verlauf-Log werden eingebettete Ziffernfolgen ab
9 Stellen ersetzt (_maskiere_verlauf, gleiche Heuristik wie das bestehende
Frontend-telBlur, nur serverseitig und wirklich ersetzend statt nur
verdeckend). Die echten Werte verlassen den Server in diesem Modus nicht
mehr.

Getestet: 13 unmaskierte Rufnummern im Verlauf bei Passwort-Zugriff, 0 bei
Token-Zugriff; _maskiere_anrufe() isoliert verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
This commit is contained in:
Jeuner 2026-08-25 02:11:41 +02:00
parent 7dcb90826c
commit abf54765d8

View file

@ -229,6 +229,30 @@ def verlauf(anzahl: int = 150) -> list[dict]:
return alle[-anzahl:]
# Bei Testzugang (kein Passwort, oeffentlicher Link) werden Rufnummern nicht
# nur im Frontend per CSS geblurred (das verdeckt nur die Anzeige - im
# Seitenquelltext/DevTools/Netzwerk-Tab stehen sie trotzdem echt drin),
# sondern schon hier serverseitig ersetzt. Die echten Werte verlassen den
# Server in diesem Modus gar nicht erst.
_MASKE = "•••• •••• •••"
def _maskiere_anrufe(anrufe_liste: list[dict]) -> list[dict]:
for a in anrufe_liste:
if a.get("nummer"):
a["nummer"] = _MASKE
if a.get("rueckrufnummer"):
a["rueckrufnummer"] = _MASKE
return anrufe_liste
def _maskiere_verlauf(eintraege: list[dict]) -> list[dict]:
for e in eintraege:
if e.get("text"):
e["text"] = re.sub(r"\d{9,}", "•••••••••", e["text"])
return eintraege
class Handler(BaseHTTPRequestHandler):
def log_message(self, *_): # kein Zugriffslog auf der Konsole
pass
@ -307,9 +331,15 @@ class Handler(BaseHTTPRequestHandler):
daten["testzugang"] = getattr(self, "_via_token", False)
self._json(daten)
elif pfad == "/api/anrufe":
self._json({"stapel": stapel.erlaubt(), "anrufe": anrufe()})
liste = anrufe()
if getattr(self, "_via_token", False):
liste = _maskiere_anrufe(liste)
self._json({"stapel": stapel.erlaubt(), "anrufe": liste})
elif pfad == "/api/verlauf":
self._json(verlauf(150))
eintraege = verlauf(150)
if getattr(self, "_via_token", False):
eintraege = _maskiere_verlauf(eintraege)
self._json(eintraege)
elif pfad == "/api/modell":
self._json({"aktuell": modellwahl.aktuelle_id(), "optionen": modellwahl.AUSWAHL})
elif pfad.startswith("/audio/"):