implement CSRF protection with origin validation in API routes, fixes #570

This commit is contained in:
Raj Nandan Sharma
2026-03-12 22:54:39 +05:30
parent 1f352591a4
commit fb7939a4dc
2 changed files with 41 additions and 1 deletions
+38 -1
View File
@@ -1,4 +1,5 @@
import { json, type Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
import { VerifyAPIKey } from "$lib/server/controllers/apiController";
import db from "$lib/server/db/db";
import type { UnauthorizedResponse, NotFoundResponse } from "$lib/types/api";
@@ -58,7 +59,41 @@ function extractPagePath(pathname: string): string | null {
return match ? decodeURIComponent(match[1]) : null;
}
export const handle: Handle = async ({ event, resolve }) => {
// Content types that indicate a form submission (mirrors SvelteKit's internal CSRF check scope)
const FORM_CONTENT_TYPES = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
function isFormContentType(request: Request): boolean {
const type = request.headers.get("content-type")?.split(";", 1)[0].trim()?.toLowerCase() ?? "";
return FORM_CONTENT_TYPES.includes(type);
}
// Custom CSRF handler: validates Origin when present, allows requests when absent.
// When Origin is absent (e.g. Referrer-Policy: no-referrer), security relies on
// SameSite=Lax cookies which prevent cross-site POST from carrying auth cookies.
const csrfHandle: Handle = async ({ event, resolve }) => {
const { request } = event;
if (
isFormContentType(request) &&
(request.method === "POST" ||
request.method === "PUT" ||
request.method === "PATCH" ||
request.method === "DELETE")
) {
const requestOrigin = request.headers.get("origin");
if (requestOrigin) {
const requestHost = new URL(requestOrigin).host;
const expectedHost = event.url.host;
if (requestHost !== expectedHost) {
return new Response(`Cross-site ${request.method} form submissions are forbidden`, { status: 403 });
}
}
}
return resolve(event);
};
const apiAuthHandle: Handle = async ({ event, resolve }) => {
const { pathname } = event.url;
// Check if this is an API route that requires authentication
@@ -160,3 +195,5 @@ export const handle: Handle = async ({ event, resolve }) => {
response.headers.delete("Link");
return response;
};
export const handle = sequence(csrfHandle, apiAuthHandle);
+3
View File
@@ -17,6 +17,9 @@ const config = {
paths: {
base: basePath,
},
csrf: {
trustedOrigins: ["*"],
},
},
compilerOptions: {