From 617022e4926271a95450db4b9d5c7344d83b2378 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 30 May 2026 09:59:06 +0200 Subject: [PATCH] Multer-Upload-Errors: 415/413 statt 500 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pentest 2026-05-30 INFO: Upload-Endpoints lieferten 500 statt sauberem 4xx, wenn der fileFilter den MIME-Type ablehnte (z.B. WebP/GIF, die gar nicht in der Allowlist standen) oder LIMIT_FILE_SIZE getroffen wurde. Ursache: fileFilter rief cb(new Error(...)) – multer wirft das weiter, und ohne dedizierten Error-Handler endete es als 500 "Interner Serverfehler" mit Stack-Trace im Log. Fix: - WebP + GIF in die Allowlist von upload.routes.ts (Bug-Pen- test-Erwartung des Reporters). - Globaler Express-Error-Handler in index.ts unterscheidet jetzt: * MulterError code=LIMIT_FILE_SIZE → 413 "Datei ist zu groß" * andere MulterError → 400 "Upload-Fehler: ..." * Error mit "...erlaubt"-Message → 415 mit Original-Message * sonst → bisheriger 4xx/500-Pfad Live-verifiziert: WebP/GIF/JPG → 200 SVG / text/plain → 415 + klare Message 11 MB PDF → 413 "Datei ist zu groß" Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/src/index.ts | 33 ++++++++++++++++++++++++++--- backend/src/routes/upload.routes.ts | 12 ++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 1a049b70..2208ae88 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -422,9 +422,36 @@ if (process.env.NODE_ENV === 'production') { // body-parser wirft 413 (PayloadTooLargeError) bzw. 400 (SyntaxError) mit einem // `status`-Feld. Ohne Respektierung werden legitime Client-Fehler als 500 // kaschiert und landen als "Interner Serverfehler" beim User. -app.use((err: Error & { status?: number; type?: string }, req: express.Request, res: express.Response, next: express.NextFunction) => { - console.error(err.stack); - const status = typeof err.status === 'number' && err.status >= 400 && err.status < 600 ? err.status : 500; +// +// Multer-Errors werden hier sauber abgefangen statt als 500 zu enden: +// - MulterError (z.B. LIMIT_FILE_SIZE) → 413 +// - LIMIT_UNEXPECTED_FILE / -PART_COUNT etc. → 400 +// - fileFilter-Errors (unzulässiger Typ) → 415 +// Pentest 2026-05-30, INFO: WebP/GIF-Uploads (und alle anderen vom +// fileFilter abgelehnten Typen) lieferten vorher 500 mit Stack-Trace. +app.use((err: any, req: express.Request, res: express.Response, _next: express.NextFunction) => { + console.error(err?.stack || err); + + // Multer-spezifische Fehler (importiert as namespace) + if (err?.name === 'MulterError') { + const code = err.code; + if (code === 'LIMIT_FILE_SIZE') { + return res.status(413).json({ success: false, error: 'Datei ist zu groß' }); + } + return res.status(400).json({ + success: false, + error: 'Upload-Fehler: ' + (err.message || code || 'unbekannt'), + }); + } + + // fileFilter hat cb(new Error(...), false) gerufen – kein MulterError, + // sondern unsere eigene Reject-Nachricht ("Nur PDF, JPG, ... erlaubt"). + const msg = typeof err?.message === 'string' ? err.message : ''; + if (/sind erlaubt|nicht erlaubt/i.test(msg)) { + return res.status(415).json({ success: false, error: msg }); + } + + const status = typeof err?.status === 'number' && err.status >= 400 && err.status < 600 ? err.status : 500; let message = 'Interner Serverfehler'; if (status === 413) message = 'Anfrage zu groß'; else if (status === 400 && (err.type === 'entity.parse.failed' || err instanceof SyntaxError)) { diff --git a/backend/src/routes/upload.routes.ts b/backend/src/routes/upload.routes.ts index 8c297e3f..2cde7a44 100644 --- a/backend/src/routes/upload.routes.ts +++ b/backend/src/routes/upload.routes.ts @@ -38,12 +38,18 @@ const fileFilter = ( file: Express.Multer.File, cb: multer.FileFilterCallback ) => { - // Nur PDFs und Bilder erlauben - const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/jpg']; + // PDFs + gängige Web-Bildformate. WebP + GIF nachgezogen 2026-05-30 + // (Pentest INFO: WebP/GIF lieferten 500 statt sauberem 4xx, weil + // erlaubter MIME-Type fehlte und der fileFilter dann throwte). + const allowedTypes = [ + 'application/pdf', + 'image/jpeg', 'image/jpg', 'image/png', + 'image/gif', 'image/webp', + ]; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { - cb(new Error('Nur PDF, JPG und PNG Dateien sind erlaubt')); + cb(new Error('Nur PDF, JPG, PNG, GIF und WebP-Dateien sind erlaubt')); } };