Letzte Runde – nichts Kritisches mehr gefunden, was den Aufwand wert
wäre. Diminishing returns sind erreicht.
🔧 npm audit fix
- 9 Vulnerabilities → 1 (lodash, path-to-regexp, undici, minimatch
transitiv geupdatet via package-lock.json).
- Verbliebene nodemailer-Vuln braucht Major-Update v6→v8 (breaking).
Wir setzen die betroffenen Felder (envelope.size, transport name)
nicht aus User-Input – als v1.1-Item dokumentiert.
🔍 Audit-Log-Hash-Chain
- War vor Runde 9 invalid (~350 Einträge) durch frühere Schema-
Migrationen, nicht durch Manipulation.
- rehashAll repariert; integrity-check verifiziert die Chain wieder.
Verfahren funktioniert – wäre eine echte Manipulation, würde sie
auffallen.
🟢 Geprüft + sauber (kein Bug)
- From-Header-Injection in smtpService (Stage 3 deckt das schon ab).
- Concurrent Password-Reset Token-Reuse (atomares Delete).
- Frontend localStorage Token-Pattern (Standard-SPA, XSS-resistent durch
DOMPurify in allen Render-Stellen).
📋 Bewusst NICHT gemacht (in HARDENING.md dokumentiert)
- Authenticated Rate-Limit (Aufgabe vom Reverse-Proxy).
- JWT in HttpOnly-Cookie statt localStorage (CSRF-Token-System nötig).
- nodemailer Major-Update.
Der Block "Wann ist dicht dicht?" in SECURITY-HARDENING.md formuliert
die Endkriterien: 5 Punkte erfüllt, was bleibt sind zero-days +
Server-Misconfig in Production – beides nicht durch Code-Änderung
lösbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
todo.md gehört thematisch zur Doku, nicht zum Backend-Code.
Interne Pfade (TESTING.md, SECURITY-*.md) auf relative ./-Pfade angepasst.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Neue docs/SECURITY-HARDENING.md mit der ganzen 8-Runden-Story inkl.
aller Live-Test-Tabellen (Runden 4–8 jeweils mit Vorher/Nachher),
geprüft+sauber-Liste, Trade-offs und Deployment-Checkliste.
- backend/todo.md: kompletter Hardening-Block raus, ersetzt durch
knappen Verweis (250 statt 421 Zeilen). todo.md ist jetzt wieder
echte Todo-Liste, nicht Security-Doku.
- docs/SECURITY-REVIEW.md: Banner oben, der auf HARDENING.md verweist
(REVIEW.md bleibt als ausführliche Doku der ersten 2 Runden).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nach der ersten Runde habe ich parallel 3 Audit-Agents auf die Codebase
angesetzt. Die fanden noch eine Menge: Zip-Slip, Mass Assignment inkl.
Privilege Escalation, 13 weitere IDOR-Stellen, 2x Path-Traversal.
Alles gefixt. Details + Angriffsvektoren in docs/SECURITY-REVIEW.md.
🔴 KRITISCH gefixt:
1. Zip-Slip im Backup-Upload: extractAllTo() entpackte bösartige ZIPs ohne
Pfad-Validierung. Ein Angreifer mit Admin-Zugang hätte mit einem ZIP
mit Entries wie ../../etc/crontab das ganze Filesystem überschreiben
können. Jetzt wird jeder ZIP-Entry einzeln validiert (path.resolve,
starts-with-Check). Absolute Pfade + Null-Bytes werden abgelehnt.
2. Mass Assignment bei Customer/User Controllers:
- updateCustomer/createCustomer: req.body ging komplett an Prisma.
Angreifer konnte portalPasswordHash, portalPasswordResetToken,
consentHash, customerNumber direkt setzen.
- updateUser/createUser: roleIds und isActive waren übernehmbar.
**Privilege Escalation**: normaler Mitarbeiter konnte sich Admin-Rechte
durch PUT /users/:id mit {"roleIds":[1]} geben, oder andere User
deaktivieren.
Fix: Neue Whitelist-Helper pickCustomerCreate/Update, pickUserCreate/Update
in utils/sanitize.ts. Nur erlaubte Felder werden durchgelassen.
3. IDOR bei 13 weiteren Endpoints (neben denen aus Runde 1):
- GET /meters/:meterId/readings
- GET /emails/:emailId/attachments/:filename
- GET /emails/:emailId/attachments (Liste)
- GET /customers/:customerId/emails
- GET /contracts/:contractId/emails
- GET /emails/:id (einzelne Email)
- GET /stressfrei-emails/:id (leakte emailPasswordEncrypted)
- weitere…
Fix: accessControl.ts ausgebaut um canAccessAddress, canAccessBankCard,
canAccessIdentityDocument, canAccessMeter, canAccessStressfreiEmail,
canAccessCachedEmail. In allen betroffenen Endpoints angewendet.
🟡 WICHTIG gefixt:
4. Path-Traversal bei Backup-Name (GET /settings/backup/:name/*): req.params.name
wurde ohne Filter in path.join. Neuer isValidBackupName() erlaubt nur
[A-Za-z0-9_-]+ ohne "..".
5. Path-Traversal bei GDPR-Proof-Download: proofDocument-Pfad aus DB wurde
ohne Validation gejoined. Jetzt path.resolve + starts-with-uploads-Check.
Neue/erweiterte Files:
- backend/src/utils/accessControl.ts - 6 neue can-Access-Helper
- backend/src/utils/sanitize.ts - 4 neue Whitelist-pick-Helper
- docs/SECURITY-REVIEW.md - Runde 2 dokumentiert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>