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>
This commit is contained in:
2026-09-03 21:17:12 +02:00
co-authored by Claude Opus 5
parent 3a50a40ad5
commit cca242119b
6 changed files with 339 additions and 60 deletions
+56
View File
@@ -97,6 +97,62 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🔢 R188: Ungültige IDs im Pfad zentral statt 181-mal** (2026-09-03)
- Meldung der Pentesterin: `GET /api/users/:id` gibt bei nicht-numerischer
ID **500** statt 400 (`/api/users/permissions` trifft `/:id`). Ihr Patch
setzt einen Guard in `getUser`.
- **Berechtigt und größer als gemeldet.** Ihr Fix schließt nebenbei etwas,
das sie nicht beansprucht: `parseInt('12abc')` ergibt **12**, also lieferte
`GET /api/users/12abc` bisher Benutzer 12 aus. Und es waren **181**
ungeprüfte Stellen in 19 Controllern, nicht eine.
- 181 Einzel-Guards wären genau der Fehler aus R186-01 gewesen (drei
Filterlisten, die auseinanderliefen). Stattdessen `router.param()`, an
**einer** Stelle für alle 33 Router registriert über einen `mounte()`-
Helfer, der Prüfung und Einhängen zusammenbindet. Wer künftig `app.use`
schreibt statt `mounte`, umgeht sie nicht versehentlich, sondern sichtbar.
- **Antwort ist 404, nicht 400:** Ein Pfadsegment, das keine ID sein kann,
benennt keine Ressource. Der bestehende Präzedenzfall
(`provider.controller.ts`, Pentest Mai 2026) hatte es genauso entschieden.
- **Eine ältere Heuristik abgelöst** (Pentest Runde 7): Sie blockte
`^\d+[a-zA-Z]+$` im Pfad ihr eigener Kommentar nannte den Grund,
`app.param()` greift nicht auf in Sub-Router gemounteten Routes", und
genau das löst `mounte()`. Sie war **zu eng** (`/users/abc` ging durch →
500) und **zu weit** (ein Einstellungs-Schlüssel `12abc` unter `:key` wurde
geblockt, obwohl das keine ID ist), und sie antwortete 400, wo jetzt 404
steht zwei Antworten für dieselbe Eingabeklasse.
- Getestet über HTTP: `abc`, `permissions`, `12abc`, `6abc`, `0`, `-1`,
`007`, `1e3`, Überlauf, `1;DROP` → alle **404**; `1` → 200. Quer über
sechs Controller gleich. Nicht-ID-Parameter (`roles/list`,
`permissions/list`, `settings/:key`) unverändert.
- Dateien: `backend/src/middleware/routeIds.ts` (neu), `backend/src/index.ts`
- [x] **⚖️ R189-01: DSGVO-Rechte hingen an keinem Konto** (2026-09-03)
- Meldung: `gdpr:*` und `audit:read/export` hängen an den Rollen DSGVO und
Developer die Admin-Rolle hat sie **nicht**, und nach einem frischen Seed
war DSGVO **keinem Konto** zugewiesen. Auskunft nach Art. 15 und Löschung
nach Art. 17 konnte damit niemand ausführen. Ausfall mit Fristwirkung.
- Seed weist `admin@admin.com` jetzt zusätzlich die DSGVO-Rolle zu. Die
Admin-**Rolle** bekommt diese Rechte weiterhin nicht die Trennung aus
R186 bleibt, nur das eine Bootstrap-Konto trägt beides.
- Label ehrlich gemacht: „Voller Zugriff auf alle **Fachfunktionen** (ohne
Audit & Datenschutz dafür die separaten Rollen DSGVO und Audit-Betrieb)".
- **Eigener Fund beim Prüfen ihres Patches:** `seed.ts` vergab an die
DSGVO-Rolle weiterhin `audit:*` **komplett**, inklusive `audit:admin`
also die Bündelung, die in `fc6f39e` aufgelöst wurde. Ich hatte damals zwei
Listen gefunden (`sync-roles.ts`, `user.service.ts`) und die dritte
übersehen. Gerettet hat es nur die Reihenfolge im Containerstart; ein
einzelnes `npm run db:seed` brachte sie zurück. Korrigiert.
- **Der Seed hilft aber nur bei Neuinstallation** (`update: {}`). Deshalb
zusätzlich eine Wache beim Start: Gibt es für `gdpr:export`, `gdpr:delete`
oder `audit:read` **kein aktives Konto**, steht das mit Handlungsanweisung
im Log. Erkennt die *Abwesenheit* einer Fähigkeit dasselbe Muster wie der
Heartbeat. Bewusst nur melden, nicht automatisch vergeben.
- Getestet: frischer Seed → Admin hat `audit:read, audit:export, gdpr:*` und
**kein** `audit:admin`. Rolle entzogen → Warnung erscheint mit allen drei
Rechten. Rolle zurück → still.
- Dateien: `backend/prisma/seed.ts`, `backend/prisma/sync-roles.ts`,
`backend/src/services/pflichtrechte.service.ts` (neu), `backend/src/index.ts`
- [x] **🕳️ Entfernter Protokoll-ANFANG wurde nicht erkannt** (2026-08-26)
- Gefunden bei der Vorbereitung des Prod-Siegels: Die Verkettung wird
zeilenweise gegen die Vorgängerin geprüft die **erste** Zeile hat keine,