diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts index 3b5d60c..8c6f00d 100644 --- a/desktop/electron/main.ts +++ b/desktop/electron/main.ts @@ -12,7 +12,7 @@ // Everything chain-related (HTTP / WS / crypto) still runs in the // renderer — Electron main stays a thin shell + native capabilities. -import { app, BrowserWindow, shell, ipcMain, dialog, safeStorage } from 'electron'; +import { app, BrowserWindow, shell, ipcMain, dialog, safeStorage, session } from 'electron'; import * as path from 'node:path'; import * as fs from 'node:fs/promises'; @@ -20,6 +20,35 @@ const isDev = !!process.env.VITE_DEV_SERVER_URL; let mainWindow: BrowserWindow | null = null; +// Content-Security-Policy is set here (not in ) so we can diverge +// dev vs. production: Vite's HMR uses eval() which needs 'unsafe-eval', +// but shipping that in a release build would earn us a security warning +// from Electron and weaken XSS defence for no good reason. +function installCSP(): void { + const policy = isDev + ? // Dev: permissive enough for Vite HMR (eval + WS) while still + // denying random remote scripts. connect-src is wide-open because + // the user picks their own node URL at runtime. + "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; " + + "connect-src 'self' ws: wss: http: https:; " + + "img-src 'self' data: blob: http: https:;" + : // Prod: no eval, no remote scripts. connect-src stays open so the + // user can target any node they configure. + "default-src 'self'; " + + "script-src 'self'; " + + "style-src 'self' 'unsafe-inline'; " + + "connect-src 'self' ws: wss: http: https:; " + + "img-src 'self' data: blob: http: https:;"; + session.defaultSession.webRequest.onHeadersReceived((details, cb) => { + cb({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': [policy], + }, + }); + }); +} + function createWindow(): void { mainWindow = new BrowserWindow({ width: 1280, @@ -134,6 +163,7 @@ ipcMain.handle('app:platform', async () => process.platform); // ── Lifecycle ───────────────────────────────────────────────────────── app.whenReady().then(() => { + installCSP(); createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); diff --git a/desktop/index.html b/desktop/index.html index 40c8802..5309a15 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -2,8 +2,11 @@ - + DChain