import type { Handle } from '@sveltejs/kit'; /** * Server-Hook für Security-Headers auf jeder ausgelieferten Response. * * Cloudflare-Tunnel-Transform-Rules dürfen das gerne nochmal * obendrauf setzen — die hier sind die Defense-Tiefe-Schicht für * den Fall, dass die Cloudflare-Schiene aus dem Pfad fällt. * * CSP läuft initial im **Report-Only-Mode**: Browser melden * Violations in die DevTools-Console (`console.warn`), die App * bleibt funktional. Nach 1-2 Tagen Live-Beobachtung muss die * Policy mit echten Reports getunet und auf Enforce geflippt * werden — `CARDS_CSP_ENFORCE=true` aktiviert den scharfen Header. */ const CSP_POLICY = [ "default-src 'self'", // 'unsafe-inline' für Svelte-5-Style-Attribute + Theme-CSS-Variables; // langfristig durch nonce-/hash-basierten Style ersetzen. "style-src 'self' 'unsafe-inline'", "script-src 'self'", "img-src 'self' data: blob:", "font-src 'self' data:", // XHR/fetch-Ziele: eigene API + Auth-Portal + LLM/Share/MCP-Plattform. "connect-src 'self' https://cardecky-api.mana.how https://auth.mana.how https://share.mana.how https://mcp.mana.how", "media-src 'self' blob:", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self' https://auth.mana.how", "object-src 'none'", ].join('; '); export const handle: Handle = async ({ event, resolve }) => { const response = await resolve(event); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); const cspHeader = process.env.CARDS_CSP_ENFORCE === 'true' ? 'Content-Security-Policy' : 'Content-Security-Policy-Report-Only'; response.headers.set(cspHeader, CSP_POLICY); if (process.env.NODE_ENV === 'production') { response.headers.set('Strict-Transport-Security', 'max-age=15552000; includeSubDomains'); } return response; };