Files
Mangalord/frontend/svelte.config.js
MechaCat02 5784483a57 fix: add CSP and defense-in-depth security response headers
Document responses carried no CSP, X-Frame-Options, Referrer-Policy, or
Permissions-Policy — leaving clickjacking and zero script-injection defense
in depth (security audit T4).

- svelte.config.js: enable kit.csp hash mode. SvelteKit hashes its own inline
  hydration scripts; the inline theme <script> in app.html isn't part of
  %sveltekit.head%, so its sha256 is pinned by hand (csp-config.js) and added
  to script-src alongside object-src 'none', base-uri 'self', frame-ancestors
  'none'. Styles stay unconstrained (Svelte emits dynamic inline style= attrs).
- hooks.server.ts: applySecurityHeaders() sets X-Frame-Options: DENY,
  Referrer-Policy: strict-origin-when-cross-origin, X-Content-Type-Options:
  nosniff, and a Permissions-Policy locking down unused features, only when the
  response hasn't already set them.
- src/csp-theme-hash.test.ts: drift guard against editing the theme script
  without updating THEME_SCRIPT_HASH.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 19:27:27 +02:00

36 lines
1.5 KiB
JavaScript

import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { THEME_SCRIPT_HASH } from './csp-config.js';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({ out: 'build' }),
// Content-Security-Policy. `mode: 'hash'` makes SvelteKit hash the
// inline scripts IT injects (the hydration bootstrap) and append those
// hashes to `script-src`. It does NOT hash the theme <script> in
// app.html — that template script isn't part of `%sveltekit.head%`, so
// we pin its sha256 here by hand (THEME_SCRIPT_HASH). If that script is
// edited, the hash drifts and the theme-flash guard would be silently
// CSP-blocked; `svelte.config.test.js` recomputes the hash from
// app.html and fails if it no longer matches this constant.
// Styles are left unconstrained (Svelte emits dynamic inline `style=`
// attributes); this policy is clickjacking + script-injection defense
// in depth, not a full lockdown. The non-CSP defense-in-depth headers
// (X-Frame-Options, Referrer-Policy, Permissions-Policy) live in
// hooks.server.ts.
csp: {
mode: 'hash',
directives: {
'script-src': ['self', THEME_SCRIPT_HASH],
'object-src': ['none'],
'base-uri': ['self'],
'frame-ancestors': ['none']
}
}
}
};
export default config;