diff --git a/frontend/src/components/ui/PdfDragButton.tsx b/frontend/src/components/ui/PdfDragButton.tsx index dfd41e0b..8c43706c 100644 --- a/frontend/src/components/ui/PdfDragButton.tsx +++ b/frontend/src/components/ui/PdfDragButton.tsx @@ -23,18 +23,19 @@ import { authApi, getAccessToken } from '../../services/api'; * Fallback (Datei im neuen Tab öffnen) bleibt aber nutzbar. * * SICHERHEIT (Pentest R131): - * - Es wird AUSSCHLIESSLICH `DownloadURL` gesetzt, KEINE `text/plain`- - * oder `text/uri-list`-Repräsentation. `DownloadURL` ist ein Chromium- - * internes Nativ-Format, das beim Fehl-Drop in ein Web-Textfeld nicht - * als lesbarer Text ankommt. Damit kann – anders als in der ersten - * Version – kein Token als Klartext in Chat/Ticket/URL-Leiste leaken. - * - In die URL wird bevorzugt ein KURZLEBIGER 60s-Download-Token + * - In die URL wird ein KURZLEBIGER 60s-Download-Token * (`authApi.getDownloadToken()`, `type:"download"`, nur `?token=` - * nutzbar) geschrieben statt des 15-Min-Access-Tokens. Er wird - * proaktiv (mount + hover) vorgewärmt. Nur im seltenen Rennen „Drag - * bevor der Token da ist" fällt es auf den Access-Token zurück – - * und auch dann nur innerhalb von DownloadURL, nie als Klartext. - * Der Drag wird NIE abgebrochen (sonst käme im Ziel keine Datei an). + * nutzbar) geschrieben statt des 15-Min-Access-Tokens mit vollen + * Rechten. Er wird proaktiv (mount + hover) vorgewärmt. Das ist der + * wesentliche Fix (Token-SCOPE): ein Fehl-Drop kann höchstens eine 60s + * gültige Downloads-only-URL zeigen statt eines Voll-Access-Tokens. + * - Es werden im Normalfall bewusst alle drei Formate gesetzt + * (`DownloadURL` für Explorer/Outlook-Desktop, `text/uri-list` + + * `text/plain` für web-basierte Mail-/Editor-Ziele) – sonst reagiert + * das Ziel-Fenster beim Drop nicht. Nur im seltenen Rennen „Drag bevor + * der Token da ist" wird ausschließlich `DownloadURL` mit Access-Token- + * Fallback gesetzt (Nativ-Format, kein Klartext-Leak). Der Drag wird + * NIE abgebrochen (sonst käme im Ziel keine Datei an). */ interface PdfDragButtonProps { /** Server-Pfad der Datei (`documentPath`). Ohne Pfad wird nichts gerendert. */ @@ -114,23 +115,38 @@ export default function PdfDragButton({ const name = safeBase.includes('.') ? safeBase : `${safeBase}${ext ? `.${ext}` : ''}`; const handleDragStart = (e: React.DragEvent) => { - // Bevorzugt den kurzlebigen 60s-Download-Token (auf mount+hover - // vorgewärmt). Ist er im seltenen Rennen noch nicht da, fällt es auf den - // Access-Token zurück – NUR innerhalb von DownloadURL. Wichtig: der Drag - // wird NIE abgebrochen (sonst käme im Mail-Fenster gar keine Datei an). - const token = cachedToken || getAccessToken() || ''; - if (!cachedToken) warmDownloadToken(); // für den nächsten Drag vorwärmen - const absolute = `${window.location.origin}${fileUrl(path, { token })}`; + // Der Drag wird NIE abgebrochen (sonst käme im Ziel gar keine Datei an). + const dl = cachedToken; + const absolute = (t: string) => `${window.location.origin}${fileUrl(path, { token: t })}`; try { - // NUR DownloadURL setzen – bewusst KEINE text/plain- oder - // text/uri-list-Repräsentation. DownloadURL ist ein Chromium-internes - // Nativ-Format und wird bei einem Fehl-Drop in ein Web-Textfeld nicht - // als lesbarer Text ausgegeben; so kann kein Token als Klartext leaken - // (Pentest R131). Für Explorer/Outlook ist DownloadURL das relevante - // Format – die entfernten Text-Formate waren dafür nie nötig. - e.dataTransfer.setData('DownloadURL', `${mime}:${name}:${absolute}`); + if (dl) { + // Normalfall: kurzlebiger 60s-Download-Token (auf mount+hover + // vorgewärmt). Alle drei Formate setzen, damit sowohl Desktop-Ziele + // (Explorer/Outlook via DownloadURL) als auch web-basierte Mail-/ + // Editor-Fenster (die nur text/uri-list bzw. text/plain verstehen) + // auf den Drop reagieren. + // + // Pentest R131: Der wesentliche Fix ist der Token-SCOPE – hier steckt + // NUR ein 60s gültiger Downloads-only-Token in der URL, nicht mehr + // der 15-Min-Access-Token mit vollen Rechten. Ein versehentlicher + // Drop in ein Textfeld kann also höchstens eine 60s-Downloads-URL + // zeigen (Empfehlung #1). Das Weglassen der Text-Formate (#2) war + // „optional" und hat das Ziehen ins Mail-Fenster gebrochen → bewusst + // nicht umgesetzt. + const url = absolute(dl); + e.dataTransfer.setData('DownloadURL', `${mime}:${name}:${url}`); + e.dataTransfer.setData('text/uri-list', url); + e.dataTransfer.setData('text/plain', url); + } else { + // Seltenes Rennen: Download-Token noch nicht vorgewärmt. Dann NUR + // DownloadURL mit Access-Token-Fallback setzen (Nativ-Format, wird in + // Web-Textfeldern nicht als Klartext ausgegeben) – kein Token-Leak + // als Text. Für den nächsten Drag vorwärmen. + e.dataTransfer.setData('DownloadURL', `${mime}:${name}:${absolute(getAccessToken() || '')}`); + warmDownloadToken(); + } } catch { - /* manche Browser werfen bei DownloadURL – dann passiert einfach nichts */ + /* manche Browser werfen bei DownloadURL – dann greifen die Text-Formate */ } e.dataTransfer.effectAllowed = 'copy'; };