fix: address PR review security and validation issues

- Add runtime type check for worktreePath in open-in-terminal handler
- Fix Windows Terminal detection using commandExists before spawn
- Fix xterm shell injection by using sh -c with escapeShellArg
- Use loose equality for null/undefined in useEffectiveDefaultTerminal
- Consolidate duplicate imports from open-in-terminal.js
This commit is contained in:
Stefan de Vogelaere
2026-01-18 15:38:54 +01:00
parent 9529afbbaa
commit 5d68e75541
5 changed files with 39 additions and 30 deletions

View File

@@ -374,8 +374,9 @@ export async function openInTerminal(targetPath: string): Promise<{ terminalName
await execFileAsync('osascript', ['-e', script]);
return { terminalName: 'Terminal' };
} else if (isWindows) {
// Try Windows Terminal first
try {
// Try Windows Terminal first - check if it exists before trying to spawn
const hasWindowsTerminal = await commandExists('wt');
if (hasWindowsTerminal) {
return await new Promise((resolve, reject) => {
const child: ChildProcess = spawn('wt', ['-d', targetPath], {
shell: true,
@@ -384,33 +385,32 @@ export async function openInTerminal(targetPath: string): Promise<{ terminalName
});
child.unref();
child.on('error', () => {
reject(new Error('Windows Terminal not available'));
});
setTimeout(() => resolve({ terminalName: 'Windows Terminal' }), 100);
});
} catch {
// Fall back to cmd
return await new Promise((resolve, reject) => {
const child: ChildProcess = spawn(
'cmd',
['/c', 'start', 'cmd', '/k', `cd /d "${targetPath}"`],
{
shell: true,
stdio: 'ignore',
detached: true,
}
);
child.unref();
child.on('error', (err) => {
reject(err);
});
setTimeout(() => resolve({ terminalName: 'Command Prompt' }), 100);
setTimeout(() => resolve({ terminalName: 'Windows Terminal' }), 100);
});
}
// Fall back to cmd
return await new Promise((resolve, reject) => {
const child: ChildProcess = spawn(
'cmd',
['/c', 'start', 'cmd', '/k', `cd /d "${targetPath}"`],
{
shell: true,
stdio: 'ignore',
detached: true,
}
);
child.unref();
child.on('error', (err) => {
reject(err);
});
setTimeout(() => resolve({ terminalName: 'Command Prompt' }), 100);
});
} else {
// Linux: Try common terminal emulators in order
const terminals = [
@@ -425,7 +425,11 @@ export async function openInTerminal(targetPath: string): Promise<{ terminalName
command: 'xfce4-terminal',
args: ['--working-directory', targetPath],
},
{ name: 'xterm', command: 'xterm', args: ['-e', `cd "${targetPath}" && $SHELL`] },
{
name: 'xterm',
command: 'xterm',
args: ['-e', 'sh', '-c', `cd ${escapeShellArg(targetPath)} && $SHELL`],
},
{
name: 'x-terminal-emulator',
command: 'x-terminal-emulator',

View File

@@ -556,7 +556,12 @@ async function executeTerminalCommand(terminal: TerminalInfo, targetPath: string
case 'xterm':
// XTerm: uses -e to run a shell in the directory
await spawnDetached(command, ['-e', `cd "${targetPath}" && $SHELL`]);
await spawnDetached(command, [
'-e',
'sh',
'-c',
`cd ${escapeShellArg(targetPath)} && $SHELL`,
]);
break;
default: