diff --git a/backend/src/controllers/fileDownload.controller.ts b/backend/src/controllers/fileDownload.controller.ts index fed0e70c..e595635d 100644 --- a/backend/src/controllers/fileDownload.controller.ts +++ b/backend/src/controllers/fileDownload.controller.ts @@ -87,48 +87,72 @@ export async function downloadFile(req: AuthRequest, res: Response): Promise= 8 + && head.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) + ) return 'image/png'; + if (bytesRead >= 3 && head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff) return 'image/jpeg'; + if (bytesRead >= 6 + && (head.subarray(0, 6).toString('latin1') === 'GIF87a' + || head.subarray(0, 6).toString('latin1') === 'GIF89a') + ) return 'image/gif'; + if (bytesRead >= 12 + && head.subarray(0, 4).toString('latin1') === 'RIFF' + && head.subarray(8, 12).toString('latin1') === 'WEBP' + ) return 'image/webp'; + return null; + } catch (err) { + console.warn(`[fileDownload] Magic-Byte-Read fehlgeschlagen für ${absolute}:`, err); + return null; + } finally { + if (fd !== null) { + try { fs.closeSync(fd); } catch { /* ignore */ } + } + } +} diff --git a/docs/todo.md b/docs/todo.md index 5a3dd090..3cc36bda 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,24 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🔧 Pentest R101.1 – Inline-Preview-Pfad refaktoriert + Diagnose-Log** + - Pentester R101.1 (INFO/funktional) berichtet: `?disposition=inline` + bewirkt nichts, Browser zeigt Download-Dialog. Die Logik im + `fileDownload.controller` ist eigentlich korrekt – sauberer Magic- + Byte-Check für PDF/PNG/JPEG/GIF/WebP – und liefert beim Direkttest + gegen echte Vertrags-PDFs `application/pdf`. Wir können das in Prod + aber nicht reproduzieren. + - Refaktorierung: Magic-Byte-Check in `detectSafeContentType()` + extrahiert, finally-Block schließt File-Descriptor garantiert, + Short-Read-Fälle (`bytesRead < n`) jetzt sauber geguardet. + - Sicherheits-Verhalten unverändert: bei Magic-Byte-Mismatch bleibt + es bei `Content-Disposition: attachment` (Stored-XSS-Schutz aus + R30.13). + - Neu: `console.warn`, wenn `inline` angefragt wurde, aber der + Magic-Byte-Check fehlschlägt oder der Read crasht. Damit fällt + der Fall im Prod-Log auf, falls er nochmal auftritt – bisher + war's silent. + - [x] **🔒 Pentest R97 – Attachment-Validierung im Send-Handler** - R97.1 (LOW): malformed `content` (`null`, fehlend, `true`, `""`) erzeugte 200/500 mit rohem `Buffer.from()`-Fehlertext in der