Skip to content

Commit 0089cbb

Browse files
committed
Add Content Security Policy headers (report-only mode)
Adds CSP and other security headers to all responses: - Content-Security-Policy-Report-Only: monitors violations without blocking - X-Content-Type-Options: nosniff - X-Frame-Options: DENY - Referrer-Policy: strict-origin-when-cross-origin CSP directives configured for: - Scripts: self + unsafe-inline + unsafe-eval (Monaco editor requires these) - Styles: self + unsafe-inline (Svelte scoped styles) - Connect: self + Hasura + Gateway + Action + Workspace URLs - Workers: self + blob (Monaco editor) - Images/Fonts: self + data + blob Change header to 'Content-Security-Policy' to enforce after testing.
1 parent 8eeb165 commit 0089cbb

1 file changed

Lines changed: 59 additions & 5 deletions

File tree

src/hooks.server.ts

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,67 @@ import type { ReqValidateSSOResponse } from './types/auth';
99
import { computeRolesFromCookies, computeRolesFromJWT } from './utilities/auth';
1010
import { reqGatewayForwardCookies } from './utilities/requests';
1111

12+
/**
13+
* Build Content Security Policy directives.
14+
* CSP helps prevent XSS attacks by restricting where scripts/resources can be loaded from.
15+
*/
16+
function buildCSPDirectives(): string {
17+
// Extract hostnames from URLs for connect-src
18+
const connectSources = [
19+
"'self'",
20+
env.PUBLIC_HASURA_CLIENT_URL,
21+
env.PUBLIC_HASURA_WEB_SOCKET_URL,
22+
env.PUBLIC_GATEWAY_CLIENT_URL,
23+
env.PUBLIC_ACTION_CLIENT_URL,
24+
env.PUBLIC_WORKSPACE_CLIENT_URL,
25+
].filter(Boolean);
26+
27+
return [
28+
"default-src 'self'",
29+
// 'unsafe-inline' needed for Svelte's scoped styles and Monaco editor
30+
// 'unsafe-eval' needed for Monaco editor's syntax highlighting
31+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
32+
"style-src 'self' 'unsafe-inline'",
33+
"img-src 'self' data: blob:",
34+
"font-src 'self' data:",
35+
// Workers needed for Monaco editor
36+
"worker-src 'self' blob:",
37+
`connect-src ${connectSources.join(' ')}`,
38+
"frame-ancestors 'none'",
39+
"form-action 'self'",
40+
"base-uri 'self'",
41+
"object-src 'none'",
42+
].join('; ');
43+
}
44+
45+
/**
46+
* Add security headers to response.
47+
* Uses Report-Only mode initially to gather violations without breaking functionality.
48+
* Change to 'Content-Security-Policy' to enforce after testing.
49+
*/
50+
function addSecurityHeaders(response: Response): Response {
51+
const csp = buildCSPDirectives();
52+
53+
// Use Report-Only mode to monitor violations without blocking
54+
// Change to 'Content-Security-Policy' to enforce after testing
55+
response.headers.set('Content-Security-Policy-Report-Only', csp);
56+
57+
// Additional security headers
58+
response.headers.set('X-Content-Type-Options', 'nosniff');
59+
response.headers.set('X-Frame-Options', 'DENY');
60+
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
61+
62+
return response;
63+
}
64+
1265
export const handle: Handle = async ({ event, resolve }) => {
1366
if (event.url.pathname.includes('com.chrome.devtools')) {
1467
return new Response(null, { status: 204 });
1568
}
1669
if (event.url.pathname.includes('error') || event.url.pathname.includes('oidc')) {
1770
// don't want hooks running on an error page
18-
return await resolve(event);
71+
const response = await resolve(event);
72+
return addSecurityHeaders(response);
1973
}
2074
if (
2175
env.PUBLIC_AUTH_OIDC_ENABLED === 'true' &&
@@ -30,17 +84,17 @@ export const handle: Handle = async ({ event, resolve }) => {
3084

3185
try {
3286
if (env.PUBLIC_AUTH_OIDC_ENABLED === 'true') {
33-
return await handleOIDCAuth({ event, resolve });
87+
return addSecurityHeaders(await handleOIDCAuth({ event, resolve }));
3488
} else if (env.PUBLIC_AUTH_SSO_ENABLED === 'true') {
35-
return await handleSSOAuth({ event, resolve });
89+
return addSecurityHeaders(await handleSSOAuth({ event, resolve }));
3690
} else {
37-
return await handleJWTAuth({ event, resolve });
91+
return addSecurityHeaders(await handleJWTAuth({ event, resolve }));
3892
}
3993
} catch (e) {
4094
event.locals.user = null;
4195
}
4296

43-
return await resolve(event);
97+
return addSecurityHeaders(await resolve(event));
4498
};
4599

46100
/**

0 commit comments

Comments
 (0)