From ead422a648d6afcbe14a266e4c0b757d799675d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 17:37:39 +0000 Subject: [PATCH] Add Content Security Policy and missing security headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a shared securityHeaders object applied to both the Vite dev server and vite preview server. Also introduces serve.json so that `npm run serve` (the `serve` static server) sends the same headers in production. New headers beyond the existing COOP/COEP: - Content-Security-Policy: locks resources to same-origin with targeted exceptions for WASM eval, blob workers (FFmpeg/OpenCV), blob media URLs, inline styles (set via JS element.style throughout the UI), and data: icons. - X-Content-Type-Options: nosniff — prevents MIME-type sniffing attacks. - X-Frame-Options: DENY — clickjacking protection (belt-and-suspenders with the frame-ancestors CSP directive). https://claude.ai/code/session_016gFzZ4udLP6DGt4fwqN2wG --- serve.json | 17 +++++++++++++++++ vite.config.js | 42 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 serve.json diff --git a/serve.json b/serve.json new file mode 100644 index 0000000..f526264 --- /dev/null +++ b/serve.json @@ -0,0 +1,17 @@ +{ + "headers": [ + { + "source": "**", + "headers": [ + { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" }, + { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }, + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src blob: 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; media-src 'self' blob:; connect-src 'self' blob:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" + }, + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "X-Frame-Options", "value": "DENY" } + ] + } + ] +} diff --git a/vite.config.js b/vite.config.js index 48368a0..4432ba5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -65,16 +65,48 @@ function serveVideos() { }; } +/** + * Security headers required for: + * - COOP/COEP: SharedArrayBuffer (FFmpeg WASM) + * - CSP: restrict resource loading to same-origin + known blob/data exceptions + * - X-Content-Type-Options: prevent MIME sniffing + * - X-Frame-Options: prevent clickjacking (belt-and-suspenders alongside frame-ancestors) + */ +const securityHeaders = { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + "Content-Security-Policy": [ + "default-src 'self'", + // 'wasm-unsafe-eval' is required for FFmpeg/OpenCV WASM modules + "script-src 'self' 'wasm-unsafe-eval'", + // FFmpeg and OpenCV spin up blob: workers + "worker-src blob: 'self'", + // Inline styles are set via element.style / cssText throughout the UI layer + "style-src 'self' 'unsafe-inline'", + // blob: for video playback and CSV download; data: for icons + "img-src 'self' blob: data:", + // blob: for webcam recordings and loaded video files + "media-src 'self' blob:", + // blob: for fetch inside workers; 'self' for local video middleware + "connect-src 'self' blob:", + "font-src 'self'", + "object-src 'none'", + "base-uri 'self'", + "frame-ancestors 'none'", + ].join("; "), + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", +}; + // https://vitejs.dev/config/ export default defineConfig({ // So the build can be served from an arbitrary path base: "./", server: { - headers: { - // Required for SharedArrayBuffer used by @ffmpeg/ffmpeg - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Embedder-Policy": "require-corp", - }, + headers: securityHeaders, + }, + preview: { + headers: securityHeaders, }, plugins: [ serveVideos(),