From e792fe4185d743d19eb4bd3a64f93e4df1c23515 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 3 Jun 2026 17:54:38 +0200 Subject: [PATCH] assertSafePdf: PDF-Streams vor Pattern-Scan ausblenden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage-Bug: User lädt zwei Handy-JPGs als PDF hoch → 415 mit "PDF enthält JavaScript-Action". Die JPEG-Bytes im jsPDF-Output enthielten zufällig die Byte-Folge "/JavaScript" → Pattern-Match auf Binär-Daten statt PDF-Struktur. Fix: stream..endstream-Blöcke vor dem Scan rauspatchen. Echte PDF-Actions stehen IMMER außerhalb von Streams (Object-Dictionaries), Binär-Streams (Bilder/Fonts/Komprimiertes) werden ignoriert. Smoke-Test: jspdf-Style-PDF mit /JavaScript-Bytes im Stream durchgewinkt, echte /OpenAction /S /JavaScript blockiert, clean PDF OK. --- backend/src/utils/sanitize.ts | 9 +++++++-- docs/todo.md | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/utils/sanitize.ts b/backend/src/utils/sanitize.ts index 109abc93..c41d105a 100644 --- a/backend/src/utils/sanitize.ts +++ b/backend/src/utils/sanitize.ts @@ -276,9 +276,14 @@ export function assertSafePdf(buf: Buffer): void { if (buf.length < 5 || buf.subarray(0, 5).toString('latin1') !== '%PDF-') { return; // keine PDF → andere Validatoren zuständig } - const content = buf.toString('latin1'); + // Stream-Inhalte (Bilder/Fonts/Komprimiertes) aus dem Scan rausnehmen. + // Jpeg-Bytes können zufällig "/JavaScript" enthalten → false-positive + // bei jsPDF-generierten PDFs mit eingebetteten Fotos (stage-Bug + // 2026-06-03). Echte aktive PDF-Inhalte stehen IMMER im PDF- + // Object-Stream (außerhalb von `stream..endstream`-Blöcken). + const scanTarget = buf.toString('latin1').replace(/stream\s[\s\S]*?endstream/g, ''); for (const { pattern, label } of PDF_DANGER_PATTERNS) { - if (pattern.test(content)) { + if (pattern.test(scanTarget)) { throw new ApiError( 415, `PDF enthält nicht erlaubte aktive Inhalte (${label}). Bitte ohne JavaScript / Auto-Actions / eingebettete Dateien hochladen.`, diff --git a/docs/todo.md b/docs/todo.md index df169222..2f5f15b8 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,19 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🐞 assertSafePdf: jspdf-PDFs mit JPEGs fälschlich als „JavaScript" blockiert** + - Stage-Bug: User lädt Ausweis als „JPGs → PDF" hoch → 415 mit + Meldung „PDF enthält JavaScript-Action". Backend hat den jspdf- + Output korrekt strukturell, aber die JPEG-Bytes im Image-Stream + enthielten zufällig die Byte-Folge „/JavaScript" → Pattern-Match. + - Fix: vor dem Pattern-Scan `stream..endstream`-Blöcke aus dem + PDF-Text rausnehmen. Echte aktive Inhalte stehen IMMER außerhalb + von Streams (in PDF-Object-Dictionaries) – Binär-Streams enthalten + Bilder/Fonts/Komprimiertes und werden jetzt zu Recht ignoriert. + - Smoke-Test: jspdf-Style-PDF mit `/JavaScript`-Bytes im Stream + durchgewinkt, echte `/OpenAction /S /JavaScript` weiterhin + blockiert, clean PDF weiterhin OK. + - [x] **🐞 AddressModal: Straße-Feld ließ sich nicht editieren** - `setFormData` wurde unbedingt im Render-Body aufgerufen, wenn `formData.street !== address.street`. Jeder Tastendruck löste neu