Commit Graph
8 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 5 cca242119b R188/R189-01 aus dem Pentest umgesetzt - Klasse statt Instanz
Beide Findings der Pentesterin waren berechtigt. Ihre Patches liessen
sich nicht anwenden (Basis 791711c, seitdem 42 Commits, sync-roles.ts
kollidiert), und an zwei Stellen greifen sie zu kurz.

R188 - ungueltige IDs im Pfad
-----------------------------
Gemeldet: GET /api/users/:id gibt bei nicht-numerischer ID 500 statt 400.
Ihr Fix schliesst nebenbei mehr, als sie beansprucht: parseInt('12abc')
ergibt 12, also lieferte /api/users/12abc bisher Benutzer 12 aus.

Es waren aber 181 ungepruefte Stellen in 19 Controllern, nicht eine. 181
Einzel-Guards waeren genau der Fehler aus R186-01 gewesen - drei
Filterlisten, die dasselbe bedeuten sollten und auseinanderliefen.
Stattdessen router.param(), an einer Stelle fuer alle 33 Router
registriert, ueber einen mounte()-Helfer, der Pruefung und Einhaengen
zusammenbindet.

Antwort ist 404, nicht 400: Ein Pfadsegment, das keine ID sein kann,
benennt keine Ressource. Der bestehende Praezedenzfall in
provider.controller.ts (Pentest Mai 2026) hatte es genauso entschieden.

Dabei eine aeltere Heuristik abgeloest (Pentest Runde 7). Ihr eigener
Kommentar nannte den Grund fuer sie - "app.param() greift nicht auf in
Sub-Router gemounteten Routes" - und genau das loest mounte(). Sie war zu
eng (/users/abc ging durch und endete als 500) und zu weit (ein
Einstellungs-Schluessel 12abc unter :key wurde geblockt, obwohl das keine
ID ist), und sie antwortete 400, wo jetzt 404 steht.

R189-01 - DSGVO-Rechte ohne Traeger
------------------------------------
Gemeldet: gdpr:* und audit:read/export haengen an DSGVO und Developer,
die Admin-Rolle hat sie nicht, und nach einem frischen Seed war DSGVO
keinem Konto zugewiesen. Auskunft nach Art. 15 und Loeschung nach Art. 17
konnte niemand ausfuehren.

Seed weist admin@admin.com jetzt zusaetzlich die DSGVO-Rolle zu; die
Admin-Rolle selbst bleibt ohne diese Rechte, die Trennung aus R186 bleibt
also erhalten. Label ehrlich gemacht.

Beim Pruefen ihres Patches ein eigener Fund: seed.ts vergab an die
DSGVO-Rolle weiterhin audit:* komplett, inklusive audit:admin - die
Buendelung, die fc6f39e aufgeloest hat. Ich hatte damals zwei Listen
gefunden und die dritte uebersehen. Gerettet hat es nur die Reihenfolge
im Containerstart; ein einzelnes `npm run db:seed` brachte sie zurueck.

Der Seed hilft nur bei Neuinstallation (update: {}). Deshalb zusaetzlich
eine Wache beim Start: Gibt es fuer gdpr:export, gdpr:delete oder
audit:read kein aktives Konto, steht das mit Handlungsanweisung im Log -
Erkennung der ABWESENHEIT einer Faehigkeit, wie beim Heartbeat. Bewusst
nur melden, nicht automatisch vergeben.

Getestet ueber HTTP gegen eine Wegwerf-Instanz: alle ID-Varianten quer
ueber sechs Controller, Nicht-ID-Parameter unveraendert, frischer Seed,
Wache mit und ohne vergebene Rechte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:17:12 +02:00
duffyduckandClaude Opus 5 c7d6b6de7e Audit-Pruefung: "manipuliert" von "Luecke" getrennt + Retention fuer Routine-Auth
Problem 1 (Deutbarkeit): verifyIntegrity warf zwei voellig unterschiedliche
Befunde in einen Topf und meldete beides als "N manipulierte Eintraege". Eine
harmlose Verkettungsluecke sah damit aus wie ein Angriff - die Meldung war im
Alltag nicht deutbar und dadurch wertlos, dasselbe Muster wie beim
Refresh-Rauschen.

Fix: Rueckgabe um tamperedEntries (Inhalt nachtraeglich veraendert, ernst) und
chainGaps (Verkettung unterbrochen durch parallele Schreibvorgaenge oder
geloeschte Zeilen, meist harmlos) erweitert. invalidEntries bleibt als Summe
erhalten. Controller formuliert die Meldung eindeutig, Frontend-API-Typ
nachgezogen.

Problem 2 (Aufbewahrung): Token-Refreshes landen seit der Entrauschung als
Authentication/LOW. Diese Kombination traf auf keine spezifische Regel und fiel
in die Auffangregel * mit 3650 Tagen - das Rauschen waere 10 Jahre aufbewahrt
worden, echte Logins nur 2. Fix: Regel Authentication/LOW mit 90 Tagen, als
idempotente Migration und im Seed.

Sensitivitaet steuert die Aufbewahrung und ist keine Alarmstufe - normale
Logins und Zugriffe auf Bankdaten/Ausweise bleiben bewusst CRITICAL, ein
Herabstufen wuerde still die Aufbewahrungsfrist verlaengern.

Verifiziert: Live-Test gegen Dev-DB - echte Manipulation einer Zeile wird als
manipuliert erkannt und nicht mit Luecken verwechselt, Ketten-Luecken bleiben
bei 7, Originalzustand exakt wiederhergestellt. tsc + vite build gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:30:36 +02:00
duffyduckandClaude Opus 4.7 8e48d3b432 Pentest 2026-05-20 LOW/INFO Sammelfix
27.1 Path-Traversal-Strings in DB:
- cleanupConsents validierte documentPath zuvor nur per stripHtml,
  ließ "../../../etc/passwd" durch. Neuer isValidDocumentPath-Check
  akzeptiert nur "/uploads/<safe>", alles andere → NULL.
- cleanupDocumentPaths scannt fünf weitere Tabellen (BankCard,
  IdentityDocument, Invoice, RepresentativeAuthorization nullable;
  ContractDocument NOT NULL → nur Report).

Orphaned User:
- reportOrphanedUsers warnt beim Container-Start vor User ohne
  Rollenzuordnung (im Permission-System unsichtbar). Löschen nicht
  automatisch wegen False-Positive-Risiko.

Seed-PW-Policy:
- generateInitialPassword() nutzte Math.random() (vorhersagbar).
  Jetzt crypto.randomInt() für Pick + Fisher-Yates-Shuffle.

PUT /users/:id mit permissions / password:
- Vorher silent-drop durch Whitelist + HTTP 200, Caller glaubte
  faelschlich, Werte waeren uebernommen. Jetzt HTTP 400 mit
  konkreter Hilfe-Message.

/api/health ohne Auth:
- Pentest-Befund INFO: bewusst so, Container-Healthcheck und
  Reverse-Proxy pingen ohne Bearer-Token. Antwort liefert nur
  {status,timestamp} – keine Version, kein DB-Status, kein
  Info-Leak. Comment im Code dokumentiert die Entscheidung.

Live-verifiziert auf dev: alle fuenf Findings durchgetestet,
jeweils mit dirty Input → erwartete Sanitization/Antwort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:49:06 +02:00
duffyduckandClaude Opus 4.7 3e1fc3eab2 Mitarbeiter-Passwörter auf 25 Zeichen (BSI-Empfehlung)
Portal-Customer-Schwellwert bleibt 12 (Handy-Eingabe → längere PWs
erhöhen Reuse-Risiko). Mitarbeiter/Admin nutzen Passwort-Manager,
für die kostet die Länge nichts.

passwordGenerator.ts:
- STAFF_MIN_PASSWORD_LENGTH = 25, PORTAL_MIN_PASSWORD_LENGTH = 12
- validatePasswordComplexity({ minLength }) parametrisiert

Mitarbeiter-Pfade auf 25:
- createUser, register, setUserPassword
- confirmPasswordReset: Audience aus Token bestimmen
  (getPasswordResetAudience), User → 25, Customer → 12. Kein
  Body-Hint, damit kein Downgrade-Trick möglich.

Portal-Pfade unverändert (default 12):
- setPortalPassword, changeInitialPortalPassword

Seed-Admin:
- 28-char Zufallspasswort (statt 16) mit allen 4 Klassen garantiert
- SEED_ADMIN_PASSWORD-ENV nur akzeptiert wenn ≥ 25 Zeichen,
  sonst Log-Warnung + Random-Fallback

Frontend:
- UserList: Hinweis "Mind. 25 Zeichen". Update + PW gleichzeitig →
  zwei API-Calls (PUT + POST /users/:id/password) statt
  Password im Body durchzuschmuggeln (Backend strippt es eh)
- PasswordResetConfirm: Hinweis "Mind. 12 (Mitarbeiter: 25)"
- userApi.setPassword(id, password) neu

Live-verifiziert:
- POST /users/6/password "Hallo123!Test" (12) → 400 "mindestens 25"
- POST /users/6/password "MeinExtremLangesPW2026!Test" → 200,
  Login mit neuem PW → success
- POST /customers/3/portal/password "Hallo123!Test" (12) → 200
- POST /users createUser mit 12-char-PW → 400 "mindestens 25"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:19:58 +02:00
duffyduckandClaude Opus 4.7 cf8c6c84c2 Security-Hardening Runde 15: Pentest Runde 12 Folge-Fixes
M2-Reste – XSS-Strings + Mass-Assignment-Settings noch in DB:
Idempotentes Cleanup-Script prisma/cleanup-xss-and-mass-assignment.ts.
Strippt HTML aus Customer/User-String-Feldern, entfernt AppSettings
ohne Whitelist-Eintrag. Wird im entrypoint.sh nach Migrations + Seed
einmalig pro Container-Start ausgeführt.

User-Update + password-Feld:
password aus USER_UPDATABLE_FIELDS raus (CREATE behält es), neuer
dedizierter Endpoint POST /api/users/:id/password mit Audit-Log
"Passwort … durch Admin gesetzt" und Komplexitäts-Check.

JS-Runtime-Fehler-Leak:
ORM_LEAK_PATTERNS um TypeError/ReferenceError/SyntaxError/RangeError +
"Cannot read properties of undefined/null" + "is not a function/
defined" erweitert. Greift im globalen res.json()-Wrapper.

POST /contracts substring-Crash:
Controller validiert type/customerId, sonst 400. generateContractNumber
fängt nullish type ab (Fallback "CON").

Seed-Admin-Passwort:
Default "admin" verletzte 12-Zeichen-Policy. Jetzt 16-char
Zufallspasswort (alle 4 Klassen garantiert via Fisher-Yates) oder per
SEED_ADMIN_PASSWORD-ENV überschreibbar. BCRYPT-Cost 12 (war 10).
Passwort wird einmalig in stdout ausgegeben mit Warnung.

AppSettings-Whitelist: companyName + defaultEmailDomain ergänzt
(kamen aus seed.ts, in 1. Whitelist vergessen).

Live-verifiziert:
- POST /contracts {} → 400 "Vertrags-Typ erforderlich" (vorher
  TypeError-Stack)
- PUT /users/6 {password:"HackerPW2026!"} → 200 aber Login mit altem
  PW geht weiter
- POST /users/6/password mit "kurz" → 400 mit Komplexitäts-Fehlern
- Cleanup-Script: planted XSS bereinigt, hackerSetting+debugMode
  entfernt, idempotenter Re-Lauf

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:09:13 +02:00
duffyduck c3edb8ad2e gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery 2026-03-21 11:59:53 +01:00
duffyduck 8c9e61cf17 added backup and email client 2026-02-01 00:02:35 +01:00
Stefan Hacker e209e9bbca first commit 2026-01-29 01:16:54 +01:00