fix(kiosk): TOCTOU-Race beim QR-Login (Audit-Finding K-1)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s

login_qr() las den Redis-Token per GET und löschte ihn danach separat per
DELETE – zwei parallele Requests mit demselben QR-Token konnten beide vor
dem Löschen lesen und je eine Session erzeugen (Doppel-Login statt echtem
Einmal-Token). Fix: GETDEL (atomar seit Redis 6.2, Server läuft 8.0.2).

K-2 (Cross-Tenant-Redeem) aus demselben Audit betrifft keinen realen
Code-Pfad – company_id-Vergleich in login_qr() war bereits korrekt, und die
Kiosk-Geräte-Enrollment "QR-Redeem"-Annahme existiert nicht als HTTP-Flow
(Enrollment läuft ausschließlich über authentifizierte CLI). Verifiziert
vor Weiterplanung der Android-Tablet-Kiosk-Architektur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-10 01:54:16 +02:00
1 parent 2e27a7fcae
commit d5b2fc75d6
1 file changed
+4 -4
+4 -4
View File
@@ -256,15 +256,15 @@ class KioskAuthService:
redis = get_async_redis()
key = QR_TOKEN_PREFIX + qr_token
try:
data = await redis.get(key)
# GETDEL statt GET+DELETE: atomar, sonst können zwei parallele
# Requests denselben Token vor dem Löschen lesen (TOCTOU, Audit-
# Finding K-1) und beide eine Session erzeugen.
data = await redis.getdel(key)
except Exception as exc:
raise HTTPException(status_code=503, detail="QR-Login nicht verfügbar (Redis).") from exc
if data is None:
raise HTTPException(status_code=401, detail="QR-Code abgelaufen oder ungültig.")
# Einmalig: Token sofort löschen
await redis.delete(key)
payload = json.loads(data)
if str(payload.get("company_id")) != str(company_id):
raise HTTPException(status_code=401, detail="QR-Code für falsches Unternehmen.")