From 5f63daa3cc57df4a5598a4f847bbbc3a83a55bb6 Mon Sep 17 00:00:00 2001 From: VDT-91 Date: Wed, 4 Feb 2026 12:18:02 +0100 Subject: [PATCH] fix(windows): use full path to where.exe for reliable executable lookup (#1659) * fix(windows): use full path to where.exe for reliable executable lookup Use full path to where.exe (C:\Windows\System32\where.exe) instead of relying on it being in PATH. This fixes issues in restricted environments or when Electron doesn't inherit the full system PATH. Changes: - Export getWhereExePath() from windows-paths.ts as single source of truth - Update getWhichCommand() in paths.ts to use the shared helper - Fix shell injection vulnerability in release-handlers.ts by using execFileSync with array arguments instead of string template - Standardize SystemRoot env var fallback (check both SystemRoot and SYSTEMROOT variants) for consistency across all usages Co-Authored-By: Claude Opus 4.5 * fix: resolve CI failures from outdated develop branch - Fix Ruff formatting in parallel_orchestrator_reviewer.py and pydantic_models.py - Fix test_integration_phase4.py to register module in sys.modules before exec_module for Python 3.12+ dataclass compatibility Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com> --- .../ipc-handlers/github/release-handlers.ts | 3 +-- apps/frontend/src/main/platform/paths.ts | 10 ++++--- apps/frontend/src/main/utils/windows-paths.ts | 27 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts b/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts index e6183e82..0330395f 100644 --- a/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts @@ -19,8 +19,7 @@ import { getWhichCommand } from '../../platform'; */ function checkGhCli(): { installed: boolean; error?: string } { try { - const checkCmd = `${getWhichCommand()} gh`; - execSync(checkCmd, { encoding: 'utf-8', stdio: 'pipe' }); + execFileSync(getWhichCommand(), ['gh'], { encoding: 'utf-8', stdio: 'pipe' }); return { installed: true }; } catch { return { diff --git a/apps/frontend/src/main/platform/paths.ts b/apps/frontend/src/main/platform/paths.ts index c1758bcb..3019c6c6 100644 --- a/apps/frontend/src/main/platform/paths.ts +++ b/apps/frontend/src/main/platform/paths.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import * as os from 'os'; import { existsSync, readdirSync } from 'fs'; import { isWindows, isMacOS, getHomebrewPath, joinPaths, getExecutableExtension } from './index'; +import { getWhereExePath } from '../utils/windows-paths'; /** * Resolve Claude CLI executable path @@ -174,7 +175,7 @@ export function getWindowsShellPaths(): Record { return {}; } - const systemRoot = process.env.SystemRoot || 'C:\\Windows'; + const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT || 'C:\\Windows'; // Note: path.join('C:', 'foo') produces 'C:foo' (relative to C: drive), not 'C:\foo' // We must use 'C:\\' or raw paths like 'C:\\Program Files' to get absolute paths @@ -297,11 +298,14 @@ export function getOllamaInstallCommand(): string { /** * Get the command to find executables in PATH * - * Windows: where.exe + * Windows: Full path to where.exe (C:\Windows\System32\where.exe) + * Using full path ensures it works even when System32 isn't in PATH, + * which can happen in restricted environments or when Electron doesn't + * inherit the full system PATH. * Unix: which */ export function getWhichCommand(): string { - return isWindows() ? 'where.exe' : 'which'; + return isWindows() ? getWhereExePath() : 'which'; } /** diff --git a/apps/frontend/src/main/utils/windows-paths.ts b/apps/frontend/src/main/utils/windows-paths.ts index 1a2c8e34..5f56f00e 100644 --- a/apps/frontend/src/main/utils/windows-paths.ts +++ b/apps/frontend/src/main/utils/windows-paths.ts @@ -130,6 +130,19 @@ export function getWindowsExecutablePaths( return validPaths; } +/** + * Get the full path to where.exe. + * Using the full path ensures where.exe works even when System32 isn't in PATH, + * which can happen in restricted environments or when Electron doesn't inherit + * the full system PATH. + * + * @returns Full path to where.exe (e.g., C:\Windows\System32\where.exe) + */ +export function getWhereExePath(): string { + const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT || 'C:\\Windows'; + return path.join(systemRoot, 'System32', 'where.exe'); +} + /** * Find a Windows executable using the `where` command. * This is the most reliable method as it searches: @@ -158,9 +171,10 @@ export function findWindowsExecutableViaWhere( } try { - // Use 'where' command to find the executable - // where.exe is a built-in Windows command that finds executables - const result = execFileSync('where.exe', [executable], { + // Use full path to where.exe to ensure it works even when System32 isn't in PATH + // This fixes issues in restricted environments or when Electron doesn't inherit system PATH + const whereExe = getWhereExePath(); + const result = execFileSync(whereExe, [executable], { encoding: 'utf-8', timeout: 5000, windowsHide: true, @@ -261,9 +275,10 @@ export async function findWindowsExecutableViaWhereAsync( } try { - // Use 'where' command to find the executable - // where.exe is a built-in Windows command that finds executables - const { stdout } = await execFileAsync('where.exe', [executable], { + // Use full path to where.exe to ensure it works even when System32 isn't in PATH + // This fixes issues in restricted environments or when Electron doesn't inherit system PATH + const whereExe = getWhereExePath(); + const { stdout } = await execFileAsync(whereExe, [executable], { encoding: 'utf-8', timeout: 5000, windowsHide: true,