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>
This commit is contained in:
@@ -1,11 +1,34 @@
|
||||
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' })
|
||||
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']
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user