From d365dd33f36400b08516fe12ec766fa53b05ef3b Mon Sep 17 00:00:00 2001 From: ivandobskygithub Date: Wed, 6 May 2026 09:10:53 +0100 Subject: [PATCH] Add Content-Security-Policy header; tighten unwatch-file guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSP: default-src 'self' with allowances for xterm's runtime inline styles (style-src 'unsafe-inline'), data: imgs, and data: fonts. No 'unsafe-inline' or 'unsafe-eval' for scripts — markdown is sanitised through DOMPurify before hitting the DOM. unwatch-file: route the path through assertPathAllowed so the IPC surface is uniform with watch-file / read-file-for-panel / save-file-for-panel. A renderer can only ever close a watcher it could have opened anyway, so this is consistency rather than a real escalation fix. Identified during a follow-up audit against upstream PR #27. The other items in #27 (path-guard on file IPCs, DOMPurify on markdown, scheduler shell-quoting) are already in place via earlier hardening commits. Co-Authored-By: Claude Opus 4.7 (1M context) --- main.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/main.js b/main.js index ebfa277..3d3c613 100644 --- a/main.js +++ b/main.js @@ -451,11 +451,12 @@ ipcMain.handle('watch-file', (_event, filePath) => { }); ipcMain.handle('unwatch-file', (_event, filePath) => { - const resolved = path.resolve(filePath); - const watcher = fileWatchers.get(resolved); + const check = assertPathAllowed(filePath, 'read'); + if (!check.ok) return { ok: false, error: check.error }; + const watcher = fileWatchers.get(check.resolved); if (watcher) { watcher.close(); - fileWatchers.delete(resolved); + fileWatchers.delete(check.resolved); } return { ok: true }; }); @@ -1417,6 +1418,31 @@ ipcMain.handle('updater-install', () => {}); // --- App lifecycle --- app.whenReady().then(() => { + // Content-Security-Policy: lock the renderer to same-origin script/style + // execution. xterm/codemirror are bundled locally; markdown is rendered + // through DOMPurify before reaching the DOM, so 'unsafe-inline' is not + // required for scripts. Inline styles are still allowed (xterm injects + // colour styles at runtime via setAttribute('style', …)). + const { session } = require('electron'); + session.defaultSession.webRequest.onHeadersReceived((details, callback) => { + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': [ + "default-src 'self'; " + + "script-src 'self'; " + + "style-src 'self' 'unsafe-inline'; " + + "img-src 'self' data: blob:; " + + "font-src 'self' data:; " + + "connect-src 'self'; " + + "object-src 'none'; " + + "base-uri 'self'; " + + "frame-ancestors 'none'" + ], + }, + }); + }); + // Seed path-guard allowed roots from every known Claude projects folder // so the file panel can read project files immediately at startup. try {