Folge-Hardening zu e1ddbf3, Cluster A2+A3 aus FEATURE_IDEAS.
* hooks.server.ts: restriktive CSP im Report-Only-Modus
(default-src 'self', script-src 'self', connect-src whitelist
auf cardecky-api/auth.mana.how/share/mcp). CARDS_CSP_ENFORCE=true
flippt auf den scharfen Header.
* docs/playbooks/SERVICE_KEY_ROTATION.md: 5-Schritt-Rotation für
CARDS_DSGVO_SERVICE_KEY bis Phase F-1 (mana-auth-managed Keys).
Forensik der Bypass-Periode 2026-05-08 → 2026-05-12 ist abgeschlossen:
nur 2 user_ids in der Cards-DB, beide legitim (tills95@gmail.com +
Smoke-Test-Sentinel c1a5, letztere via DSGVO-Endpoint aufgeräumt).
Kein ausgenutzter Bypass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.4 KiB
Service-Key-Rotation — Cards
Stand: 2026-05-12.
CARDS_DSGVO_SERVICE_KEY ist heute ein statischer Service-Key,
gegen den apps/api/src/middleware/service-key.ts constant-time-
vergleicht. mana-admin verwendet ihn für DSGVO-Fan-Out
(POST /api/v1/dsgvo/{export,delete}).
Bis Phase F-1 die Verifikation gegen mana-auth.apps.app_service_keys
ersetzt, bleibt Rotation manuell. Dieses Playbook hält den Ablauf
festgenagelt.
Wann rotieren
- Sofort bei Verdacht auf Leak (Logs, env-Datei, Container-Image, alte Backups, geleakte Screenshots).
- Routinemäßig alle 6 Monate.
- Nach Personalwechsel in der Plattform-Crew.
- Nach jeder Notfall-
docker compose exec-Session, in der der Key sichtbar war.
Rotation in 5 Schritten
1. Neuen Key generieren
openssl rand -hex 32
# Beispiel: msk_2cfa7… (Prefix nach Konvention msk_ für „Mana Service Key")
2. Neuen Key auf der Prod-Box eintragen
ssh mana-server
cd ~/projects/cards
nano infrastructure/.env.production
# CARDS_DSGVO_SERVICE_KEY=msk_<neu> ← ersetzen
.env.production ist git-ignored und liegt ausschließlich auf der
Box. Nicht ins Repo committen.
3. cards-api neustarten
docker compose -f infrastructure/docker-compose.production.yml \
--env-file infrastructure/.env.production \
up -d cards-api
Kein Rebuild nötig — env-only-Change.
4. Neuen Key bei den Callern eintragen
Aktueller Single-Caller: mana-admin.
# mana-Plattform: env-Datei für mana-admin updaten + Service neustarten
ssh mana-server
cd ~/projects/mana
nano .env.production
# CARDS_DSGVO_SERVICE_KEY_FOR_FANOUT=msk_<neu> ← oder wie auch immer
# die mana-admin-env-Variable benannt ist
docker compose -f infrastructure/docker-compose.production.yml \
up -d mana-admin
Wenn weitere S2S-Caller dazukommen (z.B. mana-research): hier ergänzen.
5. Verifizieren + alten Key invalidiert
# Alter Key MUSS jetzt 401 geben:
curl -i -H "X-Service-Key: <alter-key>" \
https://cardecky-api.mana.how/api/v1/dsgvo/export?user_id=...
# Erwartet: 401 service_key_invalid
# Neuer Key MUSS funktionieren:
curl -i -H "X-Service-Key: <neuer-key>" \
https://cardecky-api.mana.how/api/v1/dsgvo/export?user_id=...
# Erwartet: 200 mit JSON-Bundle
Audit-Log-Zeile prüfen:
docker logs cards-api --since 5m | grep '"event":"dsgvo.export"' | tail
Sollte den letzten erfolgreichen Aufruf mit auth_mode: "service-key"
zeigen.
Was nach Phase F-1 anders wird
Phase F-1 ersetzt die serviceKeyAuth()-Middleware durch
Verifikation gegen mana-auth.apps.app_service_keys. Damit:
- Keys leben in der DB, nicht in env.
- Rotation =
UPDATE apps.app_service_keys SET key_hash = … WHERE app_id = 'cards'. - Multiple Keys parallel möglich (overlap-Rotation ohne Downtime).
- Revocation via Soft-Delete-Flag.
Bis dahin: dieses Playbook.
Tabu
- Nie den Service-Key in Commit-Messages, PR-Beschreibungen oder Chat-Threads klartext hinschreiben.
- Nie im selben Step neuen Key generieren und alten Caller weiterlaufen lassen — kurz Downtime ist OK, blindes Doppel- Akzeptieren ist es nicht. (Die Middleware hat heute nur einen Slot.)
- Nie
CARDS_DSGVO_SERVICE_KEYim Compose-File hart vorgeben — bleibt env-var-Reference (${CARDS_DSGVO_SERVICE_KEY:?missing}), damit der Compose-Default „fail-loud" ist.