Der Branding-Upload akzeptierte bisher jeden Dateityp, der clientseitig mit "image/" beginnt, inkl. SVG. Anders als beim vorherigen Supabase- Storage-Bucket (andere Origin) wird die Datei jetzt auf der eigenen Origin unter /branding/[filename] ausgeliefert – eine SVG mit eingebettetem <script> hätte bei direktem Aufruf der Datei-URL (z. B. "Bild in neuem Tab öffnen") mit dem Session-Cookie eines anderen Admins ausgeführt werden können und wäre dabei auch am CSRF-Schutz vorbeigekommen (Origin stimmt ja). Upload-Route erlaubt jetzt nur noch eine Whitelist an Rasterformaten (png/jpg/webp/gif/ico), die Dateiendung wird aus dem geprüften MIME-Typ abgeleitet statt aus dem vom Client gesendeten Dateinamen. Die Ausliefer-Route kennt "svg" als Content-Type nicht mehr (Defense in Depth). Gefunden bei der Sicherheitsprüfung vor dem Merge nach master.
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { readBrandingFile } from "@/lib/branding";
|
||
|
||
// Kein svg: würde mit Content-Type image/svg+xml ausgeliefert und ist
|
||
// bei direkter Navigation auf die Datei aktiv ausführbar (eingebettetes
|
||
// <script> läuft dann same-origin inkl. Session-Cookie) – anders als bei
|
||
// Einbettung per <img>/<link rel="icon">. Nur ungefährliche Rasterformate
|
||
// zulassen, siehe auch die Whitelist beim Upload in api/admin/branding.
|
||
const CONTENT_TYPES: Record<string, string> = {
|
||
png: "image/png",
|
||
jpg: "image/jpeg",
|
||
jpeg: "image/jpeg",
|
||
webp: "image/webp",
|
||
ico: "image/x-icon",
|
||
gif: "image/gif",
|
||
};
|
||
|
||
export async function GET(
|
||
request: Request,
|
||
{ params }: { params: Promise<{ filename: string }> }
|
||
) {
|
||
const { filename } = await params;
|
||
const data = await readBrandingFile(filename);
|
||
if (!data) return new Response("Not found", { status: 404 });
|
||
|
||
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
||
const contentType = CONTENT_TYPES[ext] ?? "application/octet-stream";
|
||
|
||
return new Response(new Uint8Array(data), {
|
||
headers: {
|
||
"Content-Type": contentType,
|
||
// Dateinamen tragen bereits einen Date.now()-Suffix, ein neuer
|
||
// Upload bekommt also automatisch einen neuen Namen.
|
||
"Cache-Control": "public, max-age=31536000, immutable",
|
||
},
|
||
});
|
||
}
|