R192-01: Fehlermeldungen, die fuer Entwickler geschrieben sind
Befund der Pentesterin: PUT /users/:id {"roleIds":{}} gab 400 mit dem
rohen JS-Fehler "object is not iterable". Kein Wipe, keine Eskalation -
dieselbe Familie wie der Prisma-Pfad-Leak aus R190-01.
Zwei Ursachen. Erstens spreizte normalisiereRollenIds die roleIds ohne
Array.isArray-Guard. Zweitens reichte die Fehlerabbildung jede
error.message durch.
Die Unterscheidung laeuft jetzt ueber die Fehlerklasse, nicht ueber eine
Heuristik auf dem Text: Ein blankes Error werfen wir absichtlich und mit
einer Meldung fuer Menschen ("Der letzte Admin kann nicht..."). TypeError
und Verwandte sind Programmierfehler, Prisma-Fehler kommen von aussen -
beides sagt dem Aufrufer nichts Nuetzliches und einem Angreifer zu viel.
Unerwartetes wird 500 statt 400: Ein Eingabefehler-Code waere die naechste
Meldung, die sich falsch ausgibt.
Gegengeprueft, dass mit dem Leck nicht auch das Nuetzliche wegfaellt:
"Rolle kann nicht geloescht werden, da sie 1 Benutzern zugewiesen ist" ->
400, Systemrollen-Sperre -> 403 mit Klartext, Eskalation -> 403 mit den
konkret fehlenden Rechten. {}, "abc", 5, true als roleIds -> 400 "roleIds
muss eine Liste sein", Zustand unveraendert. R190-01/R191-01 gruen.
Offen und groesser als der Befund: Das Muster
`error instanceof Error ? error.message` steht 124-mal in 26
Controller-Dateien. Behoben ist es nur im Benutzer- und Rollenpfad.
Dieselbe Lage wie bei R188 und gehoert genauso zentral geloest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -655,21 +655,31 @@ function antworteAufRollenFehler(res: Response, error: unknown, fallback: string
|
||||
return;
|
||||
}
|
||||
|
||||
// Datenbankfehler NICHT durchreichen. Prisma haengt den vollstaendigen
|
||||
// Aufruf samt Dateipfad an die Meldung - das ging bisher wortwoertlich an
|
||||
// den Client. Nach aussen die allgemeine Auskunft, die Einzelheiten ins
|
||||
// Serverprotokoll.
|
||||
const name = error instanceof Error ? error.name : '';
|
||||
if (name.startsWith('Prisma')) {
|
||||
console.error(`[${fallback}]`, error);
|
||||
res.status(400).json({ success: false, error: fallback } as ApiResponse);
|
||||
// Ab hier gilt: Nur was wir SELBST formuliert haben, geht nach draussen.
|
||||
//
|
||||
// Vorher wurde jede `error.message` durchgereicht. Damit landete erst der
|
||||
// vollstaendige Prisma-Aufruf samt Serverpfad beim Client, und danach der
|
||||
// Wortlaut eines TypeError ("object is not iterable…") bei
|
||||
// `{"roleIds":{}}` (Pentest R190-01 und R192-01). Beide Male dieselbe
|
||||
// Ursache: eine Fehlermeldung, die fuer Entwickler geschrieben ist, an
|
||||
// einen Empfaenger, fuer den sie nicht gedacht war.
|
||||
//
|
||||
// Die Unterscheidung ist die Fehlerklasse, keine Heuristik auf dem Text:
|
||||
// Ein blankes `Error` werfen wir absichtlich und mit einer Meldung fuer
|
||||
// Menschen ("Der letzte Admin kann nicht…"). TypeError, RangeError und
|
||||
// Verwandte sind Programmierfehler, Prisma-Fehler kommen von aussen -
|
||||
// beides sagt dem Aufrufer nichts Nuetzliches und dem Angreifer zu viel.
|
||||
const istAbsichtlich = error instanceof Error && error.constructor === Error;
|
||||
if (istAbsichtlich) {
|
||||
res.status(400).json({ success: false, error: error.message } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : fallback,
|
||||
} as ApiResponse);
|
||||
// Unerwartet: Das ist ein Serverfehler, kein Eingabefehler - und ein 400
|
||||
// waere hier die naechste Meldung, die sich falsch ausgibt. Einzelheiten
|
||||
// ins Protokoll, damit sie nicht verlorengehen.
|
||||
console.error(`[${fallback}] unerwarteter Fehler:`, error);
|
||||
res.status(500).json({ success: false, error: fallback } as ApiResponse);
|
||||
}
|
||||
|
||||
export async function createRole(req: AuthRequest, res: Response): Promise<void> {
|
||||
|
||||
@@ -216,6 +216,12 @@ export function rechteDerHaken(haken: {
|
||||
* falsch als Eingabepruefung - zwei verschiedene Aufgaben.
|
||||
*/
|
||||
export async function normalisiereRollenIds(roleIds: number[]): Promise<number[]> {
|
||||
// Erst pruefen, dass es ueberhaupt eine Liste ist. Ohne das lief ein
|
||||
// `{"roleIds":{}}` in einen TypeError beim Aufspreizen, und dessen
|
||||
// Wortlaut ging an den Client (Pentest R192-01).
|
||||
if (!Array.isArray(roleIds)) {
|
||||
throw new UngueltigeEingabeError('roleIds muss eine Liste sein');
|
||||
}
|
||||
const eindeutig = [...new Set(roleIds)];
|
||||
if (eindeutig.length === 0) return [];
|
||||
if (!eindeutig.every((id) => Number.isInteger(id) && id >= 1)) {
|
||||
|
||||
@@ -100,6 +100,35 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
|
||||
|
||||
|
||||
- [x] **🤐 R192-01: Fehlermeldungen, die für Entwickler geschrieben sind** (2026-09-09)
|
||||
- Befund der Pentesterin: `PUT /users/:id {"roleIds":{}}` gab 400 mit dem
|
||||
rohen JS-Fehler `object is not iterable (cannot read property
|
||||
Symbol(Symbol.iterator))`. Kein Wipe, keine Eskalation — dieselbe Familie
|
||||
wie der Prisma-Pfad-Leak aus R190-01.
|
||||
- Zwei Ursachen, beide behoben:
|
||||
1. `normalisiereRollenIds` spreizte `roleIds` ohne `Array.isArray`-Guard.
|
||||
2. Die Fehlerabbildung reichte **jede** `error.message` durch.
|
||||
- Die Unterscheidung läuft jetzt über die **Fehlerklasse**, nicht über eine
|
||||
Heuristik auf dem Text: Ein blankes `Error` werfen wir absichtlich und mit
|
||||
einer Meldung für Menschen („Der letzte Admin kann nicht…"). `TypeError`
|
||||
und Verwandte sind Programmierfehler, Prisma-Fehler kommen von außen —
|
||||
beides sagt dem Aufrufer nichts Nützliches und einem Angreifer zu viel.
|
||||
Unerwartetes wird jetzt **500** statt 400: Ein Eingabefehler-Code wäre die
|
||||
nächste Meldung, die sich falsch ausgibt.
|
||||
- Gegengeprüft, dass mit dem Leck nicht auch das Nützliche wegfällt:
|
||||
„Rolle kann nicht gelöscht werden, da sie 1 Benutzern zugewiesen ist" → 400,
|
||||
Systemrollen-Sperre → 403 mit Klartext, Eskalation → 403 mit den konkret
|
||||
fehlenden Rechten. `{}`, `"abc"`, `5`, `true` als `roleIds` → 400
|
||||
„roleIds muss eine Liste sein", Zustand unverändert. R190-01/R191-01
|
||||
grün.
|
||||
- **⚠️ Offen, größer als der Befund:** Das Muster
|
||||
`error instanceof Error ? error.message` steht **124-mal in 26
|
||||
Controller-Dateien**. Behoben ist es nur im Benutzer-/Rollenpfad. Das ist
|
||||
dieselbe Lage wie bei R188 (181 ungeprüfte IDs) und gehört genauso
|
||||
zentral gelöst statt 124-mal einzeln — eigene Runde.
|
||||
- Dateien: `src/services/rechte.service.ts`, `src/controllers/user.controller.ts`
|
||||
|
||||
- [x] **🔗 R191-01: Ein PUT, eine Klammer** (2026-09-09)
|
||||
- Befund der Pentesterin nach dem R190-01-Fix: Nur der Rollentausch lag in
|
||||
der Transaktion, die drei Haken (DSGVO/Developer/Audit-Betrieb) liefen
|
||||
|
||||
Reference in New Issue
Block a user