From 32e0b1ab7d66370b5c2f1697708cd649ee970eea Mon Sep 17 00:00:00 2001 From: xRangerDE Date: Mon, 11 May 2026 04:16:46 +0200 Subject: [PATCH] security: open-file IPC blocks executable extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to 4.6.61. The open-file IPC handler (used by the "Open file" buttons in the queue + archive) was previously a plain shell.openPath call with only an existsSync check: if (typeof filePath !== "string" || !filePath) return false; if (!fs.existsSync(filePath)) return false; const result = await shell.openPath(filePath); shell.openPath happily launches any path the OS knows how to execute. An XSS landing through e.g. a smuggled queue item URL that reached the renderer-side openFile global function could pass `C:\\Windows\\System32\\calc.exe` and the IPC would launch calc. Added a deny-list of obvious shell-execution extensions (.exe, .bat, .cmd, .com, .ps1, .vbs, .vbe, .js, .jse, .wsf, .wsh, .scr, .msi, .msp, .lnk, .cpl, .reg, .hta, .jar, .application). Rejected calls log to debug + return false to the renderer. Media + text + image extensions remain unaffected — those open in their normal default-app viewers, which is the intended use case. show-in-folder + open-folder stay permissive on extension since they only open File Explorer (no execution). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main.ts b/src/main.ts index 60deb42..9d2ecc5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6919,9 +6919,24 @@ ipcMain.handle('open-folder', (_, folderPath: string) => { } }); +// Extensions that shell.openPath would happily execute via the system +// default. Calc.exe via XSS smuggling is the canonical example; this +// list blocks the obvious vectors. Media/text/image extensions are +// still fine — shell.openPath opens them in the OS's default viewer. +const OPEN_FILE_BLOCKED_EXTENSIONS = new Set([ + '.exe', '.bat', '.cmd', '.com', '.ps1', '.vbs', '.vbe', + '.js', '.jse', '.wsf', '.wsh', '.scr', '.msi', '.msp', + '.lnk', '.cpl', '.reg', '.hta', '.jar', '.application' +]); + ipcMain.handle('open-file', async (_, filePath: string): Promise => { if (typeof filePath !== 'string' || !filePath) return false; if (!fs.existsSync(filePath)) return false; + const ext = path.extname(filePath).toLowerCase(); + if (OPEN_FILE_BLOCKED_EXTENSIONS.has(ext)) { + appendDebugLog('open-file-rejected-extension', { ext, path: filePath.slice(0, 200) }); + return false; + } const result = await shell.openPath(filePath); // shell.openPath returns '' on success, an error string on failure. return result === '';