victor HF Staff commited on
Commit
cfa04c3
·
unverified ·
1 Parent(s): 0ab931d

Add strict security headers to fetch-url endpoint (#1993)

Browse files

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.

Files changed (1) hide show
  1. src/routes/api/fetch-url/+server.ts +12 -5
src/routes/api/fetch-url/+server.ts CHANGED
@@ -5,6 +5,14 @@ import { isValidUrl } from "$lib/server/urlSafety";
5
 
6
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
7
  const FETCH_TIMEOUT = 30000; // 30 seconds
 
 
 
 
 
 
 
 
8
 
9
  export async function GET({ url }) {
10
  const targetUrl = url.searchParams.get("url");
@@ -43,18 +51,17 @@ export async function GET({ url }) {
43
  }
44
 
45
  // Stream the response back
46
- const contentType = response.headers.get("content-type") || "application/octet-stream";
 
47
  const contentDisposition = response.headers.get("content-disposition");
48
 
49
  const headers: HeadersInit = {
50
  "Content-Type": contentType,
51
  "Cache-Control": "public, max-age=3600",
 
 
52
  };
53
 
54
- if (contentDisposition) {
55
- headers["Content-Disposition"] = contentDisposition;
56
- }
57
-
58
  // Get the body as array buffer to check size
59
  const arrayBuffer = await response.arrayBuffer();
60
 
 
5
 
6
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
7
  const FETCH_TIMEOUT = 30000; // 30 seconds
8
+ const SECURITY_HEADERS: HeadersInit = {
9
+ // Prevent any active content from executing if someone navigates directly to this endpoint.
10
+ "Content-Security-Policy":
11
+ "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'",
12
+ "X-Content-Type-Options": "nosniff",
13
+ "X-Frame-Options": "DENY",
14
+ "Referrer-Policy": "no-referrer",
15
+ };
16
 
17
  export async function GET({ url }) {
18
  const targetUrl = url.searchParams.get("url");
 
51
  }
52
 
53
  // Stream the response back
54
+ // Always return as text/plain to prevent any HTML/JS execution
55
+ const contentType = "text/plain; charset=utf-8";
56
  const contentDisposition = response.headers.get("content-disposition");
57
 
58
  const headers: HeadersInit = {
59
  "Content-Type": contentType,
60
  "Cache-Control": "public, max-age=3600",
61
+ ...(contentDisposition ? { "Content-Disposition": contentDisposition } : {}),
62
+ ...SECURITY_HEADERS,
63
  };
64
 
 
 
 
 
65
  // Get the body as array buffer to check size
66
  const arrayBuffer = await response.arrayBuffer();
67