From c6f423b5ac31bb6eb002ec4eefa2e5164001f5d7 Mon Sep 17 00:00:00 2001 From: xRangerDE Date: Mon, 11 May 2026 04:12:51 +0200 Subject: [PATCH] security: scheme-validate URLs handed to shell.openExternal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The open-external IPC was a pass-through: ipcMain.handle("open-external", async (_, url) => await shell.openExternal(url)); shell.openExternal on Windows happily resolves any URL scheme the OS knows how to launch — including file:// paths, ms-settings:, shell:, javascript:, and assorted protocol handlers. The renderer is contextIsolated + nodeIntegration: false so direct exploits are blocked, but an XSS landing through (for example) a streamer name that smuggled HTML into a renderer template would have a clean path through this IPC to launch arbitrary local executables via the OS shell. Validation gate: reject anything that isn't an http:// or https:// URL. Trim before the test so a smuggled leading/trailing whitespace attempt does not slip through. Rejected requests get a debug-log entry (truncated to 200 chars so a megabyte payload doesnt nuke the log) and return silently — the renderer caller already swallows the promise without checking, so silent-drop matches existing behaviour. Defence-in-depth. No known active exploit; just removing an unnecessary surface. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index bb7044d..60deb42 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6975,7 +6975,20 @@ ipcMain.handle('install-update', () => { }); ipcMain.handle('open-external', async (_, url: string) => { - await shell.openExternal(url); + // Only allow https / http URLs — never let the renderer push a + // file://, javascript:, or shell:-style URL through to the OS + // shell.openExternal handler. The renderer is contextIsolated + + // nodeIntegration: false, but an XSS through (e.g.) a streamer name + // smuggling a payload into a template would otherwise hand the + // attacker shell.openExternal which on Windows happily resolves + // file:///C:/Windows/System32/calc.exe. + if (typeof url !== 'string') return; + const trimmed = url.trim(); + if (!/^https?:\/\//i.test(trimmed)) { + appendDebugLog('open-external-rejected', { url: trimmed.slice(0, 200) }); + return; + } + await shell.openExternal(trimmed); }); // Tracks active standalone clip downloads so cancel-download / window-all-closed