diff --git a/index.html b/index.html index 59866cb0f9..60ae8a1781 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,13 @@ - + diff --git a/src/main.ts b/src/main.ts index 57429be911..2ae1843955 100644 --- a/src/main.ts +++ b/src/main.ts @@ -293,6 +293,7 @@ app.on('window-all-closed', () => { app.on('ready', (event, data) => { // Avoid potentially 2 ready fires if (mainWindow) return + // Create the mainWindow mainWindow = createWindow() // Set menu application to null to avoid default electron menu diff --git a/vercel.json b/vercel.json index b77340e062..b2e6b76255 100644 --- a/vercel.json +++ b/vercel.json @@ -2,7 +2,26 @@ "version": 2, "headers": [ { - "source": "/", + "source": "/(.*)", + "has": [ + { + "type": "host", + "value": "app.zoo.dev" + } + ], + "headers": [ + { + "key": "Reporting-Endpoints", + "value": "csp-reporting-endpoint=\"https://csp-logger.vercel.app/csp-report\"" + }, + { + "key": "Content-Security-Policy-Report-Only", + "value": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * blob: 'unsafe-inline'; connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev https://api.zoogov.dev wss://api.zoogov.dev; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js; report-uri https://csp-logger.vercel.app/csp-report; report-to csp-reporting-endpoint;" + } + ] + }, + { + "source": "/(.*)", "missing": [ { "type": "host", @@ -13,6 +32,14 @@ { "key": "X-Robots-Tag", "value": "noindex" + }, + { + "key": "Reporting-Endpoints", + "value": "csp-reporting-endpoint=\"https://csp-logger.vercel.app/csp-report\"" + }, + { + "key": "Content-Security-Policy-Report-Only", + "value": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * blob: 'unsafe-inline'; connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev https://api.zoogov.dev wss://api.zoogov.dev; object-src 'none'; frame-ancestors 'none'; frame-src 'self' https://vercel.live; script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js https://vercel.live/_next-live/feedback/feedback.js 'unsafe-eval'; report-uri https://csp-logger.vercel.app/csp-report; report-to csp-reporting-endpoint;" } ] } diff --git a/vite.base.config.ts b/vite.base.config.ts index 515e959eef..f96b67e90d 100644 --- a/vite.base.config.ts +++ b/vite.base.config.ts @@ -115,3 +115,107 @@ export function pluginHotRestart(command: 'reload' | 'restart'): Plugin { }, } } + +export function indexHtmlCsp(enabled: boolean): Plugin { + const csp = [ + // By default, only allow same origin. + "default-src 'self'", + // Allow inline styles and styles from the same origin. This is how we use CSS rightnow. + "style-src 'self' 'unsafe-inline'", + // Allow images from any source and inline images. We fetch user profile images from any origin. + "img-src * blob: 'unsafe-inline'", + // Allow WebSocket connections and fetches to our API. + "connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev https://api.zoogov.dev wss://api.zoogov.dev", + // Disallow legacy stuff + "object-src 'none'", + ] + + // Allow scripts from the same origin and from Plausible Analytics. Allow WASM execution. + const cspScriptBase = + "script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js" + + // frame ancestors can only be blocked using HTTP headers (see vercel.json) + const vercelCspBase = ["frame-ancestors 'none'"] + + const cspReporting = [ + 'report-uri https://csp-logger.vercel.app/csp-report', + 'report-to csp-reporting-endpoint', + ] + + const vercelCsp = + csp + .concat(vercelCspBase) + .concat([cspScriptBase]) + .concat(cspReporting) + .join('; ') + ';' + + const reportingEndpoints = { + key: 'Reporting-Endpoints', + value: 'csp-reporting-endpoint="https://csp-logger.vercel.app/csp-report"', + } + + console.log( + 'Content-Security-Policy for Vercel (prod) (vercel.json):', + vercelCsp + ) + + console.log( + JSON.stringify( + [ + reportingEndpoints, + { + key: 'Content-Security-Policy-Report-Only', + value: vercelCsp, + }, + ], + null, + 2 + ) + ) + + console.log('Content-Security-Policy for Vercel (preview) (vercel.json):') + + console.log( + JSON.stringify( + [ + reportingEndpoints, + { + key: 'Content-Security-Policy-Report-Only', + value: + csp + .concat(vercelCspBase) + .concat([ + // vercel.live is used for feedback scripts in preview deployments. + "frame-src 'self' https://vercel.live", + `${cspScriptBase} https://vercel.live/_next-live/feedback/feedback.js 'unsafe-eval'`, + ]) + .concat(cspReporting) + .join('; ') + ';', + }, + ], + + null, + 2 + ) + ) + + return { + name: 'html-transform', + transformIndexHtml(html: string) { + let indexHtmlRegex = + // + if (!enabled) { + // Web deployments that are deployed to vercel don't need a CSP in the indexHTML. + // They get it through vercel.json. + return html.replace(indexHtmlRegex, '') + } else { + return html.replace( + indexHtmlRegex, + `` + ) + } + }, + } +} diff --git a/vite.config.ts b/vite.config.ts index 2f8435a453..56a0a246bd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ import version from 'vite-plugin-package-version' import topLevelAwait from 'vite-plugin-top-level-await' import viteTsconfigPaths from 'vite-tsconfig-paths' import { configDefaults, defineConfig } from 'vitest/config' +import { indexHtmlCsp } from './vite.base.config' export default defineConfig(({ command, mode }) => { const runMillion = process.env.RUN_MILLION @@ -77,6 +78,7 @@ export default defineConfig(({ command, mode }) => { }, plugins: [ react(), + indexHtmlCsp(!process.env.VERCEL && mode === 'production'), viteTsconfigPaths(), eslint(), version(), diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts index 4cad918fe3..68210b751f 100644 --- a/vite.renderer.config.ts +++ b/vite.renderer.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from 'vite' import topLevelAwait from 'vite-plugin-top-level-await' import viteTsconfigPaths from 'vite-tsconfig-paths' -import { pluginExposeRenderer } from './vite.base.config' +import { pluginExposeRenderer, indexHtmlCsp } from './vite.base.config' // https://vitejs.dev/config export default defineConfig((env) => { @@ -23,6 +23,7 @@ export default defineConfig((env) => { // Needed for electron-forge (in npm run tron:start) optimizeDeps: { esbuildOptions: { target: 'es2022' } }, plugins: [ + indexHtmlCsp(true), pluginExposeRenderer(name), viteTsconfigPaths(), lezer(),