From 99bbac6d25e35080c32cdf48aa47f0ea6d2197d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Mu=C5=A1tar?= Date: Mon, 24 Nov 2025 14:06:39 +0100 Subject: [PATCH] Add strict security headers to fetch-url endpoint Introduces comprehensive security headers and forces 'text/plain' content type for all responses from the fetch-url API endpoint. This prevents execution of active content and mitigates risks if the endpoint is accessed directly. --- src/routes/api/fetch-url/+server.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/routes/api/fetch-url/+server.ts b/src/routes/api/fetch-url/+server.ts index 90752a0a1e5..85cbe1d7c06 100644 --- a/src/routes/api/fetch-url/+server.ts +++ b/src/routes/api/fetch-url/+server.ts @@ -5,6 +5,14 @@ import { isValidUrl } from "$lib/server/urlSafety"; const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const FETCH_TIMEOUT = 30000; // 30 seconds +const SECURITY_HEADERS: HeadersInit = { + // Prevent any active content from executing if someone navigates directly to this endpoint. + "Content-Security-Policy": + "default-src 'none'; frame-ancestors 'none'; sandbox; script-src 'none'; img-src 'none'; style-src 'none'; connect-src 'none'; media-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + "Referrer-Policy": "no-referrer", +}; export async function GET({ url }) { const targetUrl = url.searchParams.get("url"); @@ -43,18 +51,17 @@ export async function GET({ url }) { } // Stream the response back - const contentType = response.headers.get("content-type") || "application/octet-stream"; + // Always return as text/plain to prevent any HTML/JS execution + const contentType = "text/plain; charset=utf-8"; const contentDisposition = response.headers.get("content-disposition"); const headers: HeadersInit = { "Content-Type": contentType, "Cache-Control": "public, max-age=3600", + ...(contentDisposition ? { "Content-Disposition": contentDisposition } : {}), + ...SECURITY_HEADERS, }; - if (contentDisposition) { - headers["Content-Disposition"] = contentDisposition; - } - // Get the body as array buffer to check size const arrayBuffer = await response.arrayBuffer();