fix(PROJ-81): BUG-81-3 - PDF-Vorschau via pdf.js statt sandboxed iframe
Chromium/Brave zeigte leeren PDF-Bereich (PDFium/MimeHandlerView verweigert Rendering in sandbox="allow-scripts" ohne allow-same-origin, Firefox war nicht betroffen). allow-same-origin nachzurüsten kam nicht infrage: Blob-URLs erben den App-Origin, die Kombination hätte die Sandbox faktisch aufgehoben (PROJ-61-Kontext). Neu: PdfCanvasPreview.tsx rendert PDF via pdf.js selbst nach <canvas>, kein iframe/eingebettetes Dokument mehr nötig. Parsing im Worker, kein PDF-eigenes JavaScript, kein Text-/Annotationslayer im DOM. Seitenlimit 30, eigener Fehlerzweig für passwortgeschützte/beschädigte PDFs. Neue Dependency pdfjs-dist@6.2.108 - Restrisiko: PDF-Parsing läuft jetzt im App-Origin statt im Browser-Viewer-Prozess. Noch nicht im Browser verifiziert, Status bleibt In Review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WapWkrQusDuBMhaN8WyuXB
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ca693ded1b
commit
1ad1132006
@@ -77,7 +77,7 @@ Mail-Ansicht (/mail/[id])
|
||||
|
||||
### D) Abhängigkeiten (Pakete)
|
||||
|
||||
- PDF-Viewer-Bibliothek im Frontend (Anzeige im Dialog, ohne Download). Bilder benötigen keine zusätzliche Bibliothek (natives `<img>`).
|
||||
- PDF-Viewer-Bibliothek im Frontend (Anzeige im Dialog, ohne Download): **`pdfjs-dist@6.2.108`**, ergänzt im dritten Fix-Durchgang (BUG-81-3). Der zunächst genutzte browsereigene Viewer im iframe war nicht browserübergreifend nutzbar. Bilder benötigen keine zusätzliche Bibliothek (natives `<img>`).
|
||||
- Keine neuen Server-/Systemabhängigkeiten.
|
||||
|
||||
## Implementation Notes (Frontend, 2026-08-06)
|
||||
@@ -252,20 +252,49 @@ Kein Code-Fix. AC1 oben umformuliert: statt "Klick auf Anhang öffnet Vorschau"
|
||||
**Das Security-Modell bleibt unverändert:** der Server-Wert entscheidet ausschließlich, *welcher* Eintrag aus der festen Whitelist gewählt wird. Gerendert wird nach wie vor nur mit dem clientseitig erzwungenen MIME-Typ aus dieser Liste. Ein Wert außerhalb der Whitelist — insbesondere `image/svg+xml` (PROJ-61-Vektor) oder `text/html` — führt zu "keine Vorschau", nicht zum Rendern. SVG ist in `ALLOWED_MIME_TYPES` bewusst nicht enthalten.
|
||||
Ein Angreifer gewinnt dadurch nichts: er könnte über den Content-Type höchstens erreichen, dass seine endungslose Datei als PDF oder Bild *interpretiert* wird — genau die beiden Pfade, die ohnehin als sicher ausgelegt sind (erzwungener MIME-Typ, PDF im Sandbox-iframe ohne `allow-same-origin`).
|
||||
|
||||
### BUG-81-3 (Medium) — PDF-Vorschau blank in Chromium/Brave — **fixed**
|
||||
|
||||
**Befund aus dem Live-Test auf 132:** Firefox rendert die PDF-Vorschau einwandfrei, Chromium/Brave zeigt eine leere weiße Fläche — kein Fehler, kein Download-Prompt. Ursache bestätigt: Chromiums PDFium läuft als MimeHandlerView-Extension und benötigt dafür Same-Origin-Zugriff; in einem iframe mit `sandbox="allow-scripts"` ohne `allow-same-origin` verweigert er sich still. Firefox' pdf.js-basierter Viewer kommt damit klar. Kein Spec-Verstoß, sondern Chromium-spezifisches Verhalten.
|
||||
|
||||
#### Bewertung Option 1 (`allow-same-origin` ergänzen) — **verworfen**
|
||||
Die entscheidende Frage war, ob eine Blob-URL einen eigenen opaken Origin hat. **Hat sie nicht.** `URL.createObjectURL()` erzeugt eine URL der Form `blob:https://host/uuid`; der Origin des daraus geladenen Dokuments ist per Spezifikation der Origin des erzeugenden Kontexts, also unser App-Origin. Opak wird ein Frame nur durch die Sandbox selbst (`allow-same-origin` weggelassen) — nicht durch das Blob.
|
||||
|
||||
Daraus folgt: mit `sandbox="allow-scripts allow-same-origin"` auf same-origin-Inhalt wäre der Frame vollwertig App-Origin. Diese Kombination hebt die Sandbox praktisch auf, weil das eingebettete Dokument über `parent.document` an das Sandbox-Attribut selbst herankommt und sich per Re-Navigation entsandboxen kann; die HTML-Spezifikation warnt genau vor dieser Kombination. Der Frame hätte damit Zugriff auf DOM, nicht-httpOnly-Cookies, `localStorage` und credential-behaftete API-Aufrufe.
|
||||
|
||||
Man könnte einwenden, dass ein getarntes HTML-Dokument wegen des erzwungenen Blob-MIME-Typs (`application/pdf`) ohnehin nie als HTML geparst wird — das stimmt. Aber damit hinge die gesamte Absicherung an genau einer Kontrolle. Für ein Archiv mit fremdem, unkontrolliertem Mail-Inhalt und der PROJ-61-Vorgeschichte ist das die falsche Richtung.
|
||||
|
||||
#### Umgesetzt: Option 2 (pdf.js gebündelt)
|
||||
Neue Komponente `src/components/mail/PdfCanvasPreview.tsx`, neue Dependency `pdfjs-dist@6.2.108`.
|
||||
- PDF wird von pdf.js **selbst nach `<canvas>` gerendert** — kein iframe, kein eingebettetes Dokument, damit entfällt die Sandbox-Frage vollständig statt sie aufzuweichen.
|
||||
- Verhalten ist in Chromium und Firefox identisch, weil nicht mehr vom Viewer des Browsers abhängig.
|
||||
- Parsing läuft im Worker; das Worker-Bundle wird vom Build als statisches Asset emittiert (verifiziert: `.next/static/media/pdf.worker.min.*.mjs`).
|
||||
- PDF-eigenes JavaScript wird nicht ausgeführt: das Scripting-Sandbox-Bundle (`pdf.sandbox`) wird nicht geladen. `isEvalSupported` gibt es in pdfjs-dist v6 nicht mehr, weil `eval()` dort bereits vollständig entfernt wurde.
|
||||
- Es wird ausschließlich in Canvas gezeichnet, kein Text-/Annotationslayer — es gelangt also kein HTML aus dem PDF ins DOM.
|
||||
- Seitenlimit `MAX_RENDERED_PAGES = 30` mit Hinweis, damit ein PDF mit sehr vielen Seiten den Tab nicht blockiert.
|
||||
- Eigener Fehlerzweig für passwortgeschützte/beschädigte PDFs (Edge Case aus der Spec) mit Download-Hinweis; `doc.destroy()` beim Unmount.
|
||||
|
||||
Im Dialog hält jetzt `pdfBlob` das PDF, Bilder laufen unverändert über die Blob-URL (Bildpfad nicht angefasst, er funktioniert live). Der Guard gegen die BUG-81-1-Schleife nutzt jetzt `loadedRef` statt `urlRef`, damit er beide Pfade abdeckt.
|
||||
|
||||
**Restrisiko / ehrlich:** die Verlagerung des PDF-Parsings in unseren Origin heißt, dass eine Schwachstelle in pdf.js im App-Origin landet statt im Viewer-Prozess des Browsers. pdf.js ist genau für feindliche PDFs ausgelegt, parst im Worker und ohne `eval`, und die Alternative (Option 1) hätte den App-Origin ohnehin direkt preisgegeben. Die Dependency muss aber mitgepflegt werden — pdf.js ist ein regelmäßiges CVE-Ziel.
|
||||
|
||||
### Offen, wie abgestimmt (kein Code-Fix in diesem Durchgang)
|
||||
- **BUG-81-3** — PDF-Sandbox in Firefox/Safari unbelegt: nach Deploy im Browser verifizieren. Fällt das Rendering aus, braucht es einen sichtbaren Hinweis statt eines leeren weißen Rahmens.
|
||||
- **Audit-Logging für Anhang-Abrufe** (Bestandsfund aus dem Security-Audit): bewusst **nicht** hier mitgefixt, bekommt ein eigenes Ticket.
|
||||
- Die drei Backend-Fragen sind vom QA-Lauf beantwortet (nosniff nur über nginx, `Content-Disposition: attachment` gesetzt, **keine** serverseitige Content-Type-Whitelist). Für die Vorschau unkritisch, weil der Dialog den Server-Content-Type ignoriert; die empfohlene Handler-seitige Härtung ist Backend-Scope.
|
||||
|
||||
Nach den Fixes: `npx tsc --noEmit` und `npm run build` fehlerfrei.
|
||||
|
||||
### Stand nach zweitem Fix-Durchgang
|
||||
Alle im QA-Bericht gemeldeten Bugs sind erledigt: BUG-81-1 (High/Blocker), BUG-81-2 (Medium), BUG-81-4 (Low), BUG-81-5 (Low, Spec-Text), BUG-81-6 (Low). Offen bleibt allein **BUG-81-3** — die PDF-Darstellung im Sandbox-iframe ist für Firefox und Safari unbelegt und lässt sich nur im echten Browser gegen eine laufende Instanz prüfen.
|
||||
### Stand nach drittem Fix-Durchgang
|
||||
Alle im QA-Bericht gemeldeten Bugs sind erledigt: BUG-81-1 (High/Blocker), BUG-81-2 (Medium), BUG-81-3 (Medium), BUG-81-4, BUG-81-5, BUG-81-6 (Low).
|
||||
|
||||
**Deployment-Empfehlung: deploybar auf 132.** Kein offener Blocker im Code. Der Status bleibt bewusst auf **In Review** und geht erst auf Deployed, wenn nach dem Deploy verifiziert ist:
|
||||
- BUG-81-3: PDF-Vorschau in Chrome, Firefox, Safari, Edge. Rendert ein Browser nichts, braucht es einen sichtbaren Hinweis statt eines leeren weißen Rahmens.
|
||||
**Deployment-Empfehlung: erneut auf 132 deployen und nachtesten.** Der Status bleibt auf **In Review**, weil der PDF-Pfad komplett ausgetauscht wurde und der Fix bisher nur durch Build und statische Analyse belegt ist, nicht im Browser. Nach dem Deploy zu verifizieren:
|
||||
- **PDF-Vorschau in Chromium/Brave** — das war das ursprüngliche Symptom, hier muss jetzt gerendert werden.
|
||||
- **PDF-Vorschau in Firefox** — Regressionsprobe, dort funktionierte es vorher schon.
|
||||
- Mehrseitiges PDF: Seiten scrollbar, ab 30 Seiten erscheint der Kürzungshinweis.
|
||||
- Passwortgeschütztes/beschädigtes PDF: sichtbarer Fehlertext statt leerer Fläche.
|
||||
- Bild-Vorschau (JPG) unverändert funktionsfähig — der Pfad wurde bewusst nicht angefasst.
|
||||
- Gegenprobe zu BUG-81-1: fehlgeschlagene Vorschau (z.B. 404) erzeugt genau **einen** Request, keine Schleife — im Netzwerk-Tab prüfen.
|
||||
- Gegenprobe zu BUG-81-6: endungsloser PDF-Anhang bekommt einen Vorschau-Button und rendert.
|
||||
- Ladezeit gegen das Technical Requirement (< 2 s bei normaler Dateigröße) gegenprüfen — pdf.js rendert langsamer als der native Viewer.
|
||||
|
||||
## Deployment
|
||||
_To be added by /deploy_
|
||||
|
||||
Reference in New Issue
Block a user