fix(frontend): add windowsVerbatimArguments for Windows .cmd validation (ACS-252) (#1075)
* fix(frontend): add windowsVerbatimArguments for Windows .cmd validation Fixes installation scan failures on Windows when Claude Code CLI is installed via npm in paths containing spaces (e.g., nvm4w). The validateClaudeCliAsync function in claude-code-handlers.ts was missing the windowsVerbatimArguments: true option when executing .cmd files via cmd.exe, causing validation failures for paths like "D:\Program Files\nvm4w\nodejs\claude.cmd". This aligns the implementation with the working pattern already used in cli-tool-manager.ts validateClaudeAsync(). Changes: - Add ExecFileAsyncOptionsWithVerbatim type definition - Set windowsVerbatimArguments: true in execOptions for .cmd files Refs: ACS-252 * refactor: address PR review feedback - Export ExecFileAsyncOptionsWithVerbatim from cli-tool-manager.ts to avoid duplication (DRY principle) - Import type in claude-code-handlers.ts instead of redefining - Add isSecurePath validation in validateClaudeCliAsync for security Addresses review comments on PR #1075 * refactor: use top-level type imports for better consistency Replace inline import('child_process') type imports with top-level type imports from 'child_process' module for better code style consistency with other imports in the file. Addresses CodeRabbit nitpick suggestion on PR #1075. --------- Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com> Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
* - Graceful fallbacks when tools not found
|
||||
*/
|
||||
|
||||
import { execFileSync, execFile } from 'child_process';
|
||||
import { execFileSync, execFile, type ExecFileOptionsWithStringEncoding, type ExecFileSyncOptions } from 'child_process';
|
||||
import { existsSync, readdirSync, promises as fsPromises } from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
@@ -32,10 +32,10 @@ import { findHomebrewPython as findHomebrewPythonUtil } from './utils/homebrew-p
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
type ExecFileSyncOptionsWithVerbatim = import('child_process').ExecFileSyncOptions & {
|
||||
export type ExecFileSyncOptionsWithVerbatim = ExecFileSyncOptions & {
|
||||
windowsVerbatimArguments?: boolean;
|
||||
};
|
||||
type ExecFileAsyncOptionsWithVerbatim = import('child_process').ExecFileOptionsWithStringEncoding & {
|
||||
export type ExecFileAsyncOptionsWithVerbatim = ExecFileOptionsWithStringEncoding & {
|
||||
windowsVerbatimArguments?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { promisify } from 'util';
|
||||
import { IPC_CHANNELS, DEFAULT_APP_SETTINGS } from '../../shared/constants';
|
||||
import type { IPCResult } from '../../shared/types';
|
||||
import type { ClaudeCodeVersionInfo, ClaudeInstallationList, ClaudeInstallationInfo } from '../../shared/types/cli';
|
||||
import { getToolInfo, configureTools, sortNvmVersionDirs, getClaudeDetectionPaths } from '../cli-tool-manager';
|
||||
import { getToolInfo, configureTools, sortNvmVersionDirs, getClaudeDetectionPaths, type ExecFileAsyncOptionsWithVerbatim } from '../cli-tool-manager';
|
||||
import { readSettingsFile, writeSettingsFile } from '../settings-utils';
|
||||
import { isSecurePath } from '../utils/windows-paths';
|
||||
import semver from 'semver';
|
||||
@@ -38,6 +38,11 @@ async function validateClaudeCliAsync(cliPath: string): Promise<[boolean, string
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Security validation: reject paths with shell metacharacters or directory traversal
|
||||
if (isWindows && !isSecurePath(cliPath)) {
|
||||
throw new Error(`Claude CLI path failed security validation: ${cliPath}`);
|
||||
}
|
||||
|
||||
// Augment PATH with the CLI directory for proper resolution
|
||||
const cliDir = path.dirname(cliPath);
|
||||
const env = {
|
||||
@@ -56,12 +61,14 @@ async function validateClaudeCliAsync(cliPath: string): Promise<[boolean, string
|
||||
|| path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe');
|
||||
// Use double-quoted command line for paths with spaces
|
||||
const cmdLine = `""${cliPath}" --version"`;
|
||||
const result = await execFileAsync(cmdExe, ['/d', '/s', '/c', cmdLine], {
|
||||
const execOptions: ExecFileAsyncOptionsWithVerbatim = {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
windowsVerbatimArguments: true,
|
||||
env,
|
||||
});
|
||||
};
|
||||
const result = await execFileAsync(cmdExe, ['/d', '/s', '/c', cmdLine], execOptions);
|
||||
stdout = result.stdout;
|
||||
} else {
|
||||
const result = await execFileAsync(cliPath, ['--version'], {
|
||||
|
||||
Reference in New Issue
Block a user