From a8ccd7b6fb9f2b9fa6ae83a40938c0486d773abb Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Fri, 12 Dec 2025 12:19:20 +0100 Subject: [PATCH 1/3] Replace spawn shell option with explicit shell args to avoid DEP0190 warning --- packages/cli/src/utils/handleAutoUpdate.test.ts | 7 +++++-- packages/cli/src/utils/handleAutoUpdate.ts | 6 +++++- packages/cli/src/utils/sandbox.ts | 11 +++++++---- .../core/src/services/shellExecutionService.test.ts | 10 ++++------ packages/core/src/services/shellExecutionService.ts | 9 ++++++--- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/utils/handleAutoUpdate.test.ts b/packages/cli/src/utils/handleAutoUpdate.test.ts index bb88baf51..d448b9e7b 100644 --- a/packages/cli/src/utils/handleAutoUpdate.test.ts +++ b/packages/cli/src/utils/handleAutoUpdate.test.ts @@ -241,9 +241,12 @@ describe('handleAutoUpdate', () => { handleAutoUpdate(mockUpdateInfo, mockSettings, '/root', mockSpawn); expect(mockSpawn).toHaveBeenCalledWith( - 'npm i -g @qwen-code/qwen-code@nightly', + expect.stringMatching(/^(bash|cmd\.exe)$/), + expect.arrayContaining([ + expect.stringMatching(/^(-c|\/c)$/), + 'npm i -g @qwen-code/qwen-code@nightly', + ]), { - shell: true, stdio: 'pipe', }, ); diff --git a/packages/cli/src/utils/handleAutoUpdate.ts b/packages/cli/src/utils/handleAutoUpdate.ts index a0691a0c6..21ff7be63 100644 --- a/packages/cli/src/utils/handleAutoUpdate.ts +++ b/packages/cli/src/utils/handleAutoUpdate.ts @@ -12,6 +12,7 @@ import type { HistoryItem } from '../ui/types.js'; import { MessageType } from '../ui/types.js'; import { spawnWrapper } from './spawnWrapper.js'; import type { spawn } from 'node:child_process'; +import os from 'node:os'; export function handleAutoUpdate( info: UpdateObject | null, @@ -53,7 +54,10 @@ export function handleAutoUpdate( '@latest', isNightly ? '@nightly' : `@${info.update.latest}`, ); - const updateProcess = spawnFn(updateCommand, { stdio: 'pipe', shell: true }); + const isWindows = os.platform() === 'win32'; + const shell = isWindows ? 'cmd.exe' : 'bash'; + const shellArgs = isWindows ? ['/c', updateCommand] : ['-c', updateCommand]; + const updateProcess = spawnFn(shell, shellArgs, { stdio: 'pipe' }); let errorOutput = ''; updateProcess.stderr.on('data', (data) => { errorOutput += data.toString(); diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index 749d7193e..193fb9370 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -291,9 +291,8 @@ export async function start_sandbox( sandboxEnv['NO_PROXY'] = noProxy; sandboxEnv['no_proxy'] = noProxy; } - proxyProcess = spawn(proxyCommand, { + proxyProcess = spawn('bash', ['-c', proxyCommand], { stdio: ['ignore', 'pipe', 'pipe'], - shell: true, detached: true, }); // install handlers to stop proxy on exit/signal @@ -781,9 +780,13 @@ export async function start_sandbox( if (proxyCommand) { // run proxyCommand in its own container const proxyContainerCommand = `${config.command} run --rm --init ${userFlag} --name ${SANDBOX_PROXY_NAME} --network ${SANDBOX_PROXY_NAME} -p 8877:8877 -v ${process.cwd()}:${workdir} --workdir ${workdir} ${image} ${proxyCommand}`; - proxyProcess = spawn(proxyContainerCommand, { + const isWindows = os.platform() === 'win32'; + const proxyShell = isWindows ? 'cmd.exe' : 'bash'; + const proxyShellArgs = isWindows + ? ['/c', proxyContainerCommand] + : ['-c', proxyContainerCommand]; + proxyProcess = spawn(proxyShell, proxyShellArgs, { stdio: ['ignore', 'pipe', 'pipe'], - shell: true, detached: true, }); // install handlers to stop proxy on exit/signal diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index b598757a7..6ee7ed45d 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -825,10 +825,9 @@ describe('ShellExecutionService child_process fallback', () => { ); expect(mockCpSpawn).toHaveBeenCalledWith( - 'dir "foo bar"', - [], + 'cmd.exe', + ['/c', 'dir "foo bar"'], expect.objectContaining({ - shell: true, detached: false, windowsHide: true, }), @@ -840,10 +839,9 @@ describe('ShellExecutionService child_process fallback', () => { await simulateExecution('ls "foo bar"', (cp) => cp.emit('exit', 0, null)); expect(mockCpSpawn).toHaveBeenCalledWith( - 'ls "foo bar"', - [], + 'bash', + ['-c', 'ls "foo bar"'], expect.objectContaining({ - shell: 'bash', detached: true, }), ); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 2346de1b2..22d8d3c62 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -223,12 +223,15 @@ export class ShellExecutionService { ): ShellExecutionHandle { try { const isWindows = os.platform() === 'win32'; + const shell = isWindows ? 'cmd.exe' : 'bash'; + const shellArgs = isWindows + ? ['/c', commandToExecute] + : ['-c', commandToExecute]; - const child = cpSpawn(commandToExecute, [], { + const child = cpSpawn(shell, shellArgs, { cwd, stdio: ['ignore', 'pipe', 'pipe'], - windowsVerbatimArguments: true, - shell: isWindows ? true : 'bash', + windowsVerbatimArguments: isWindows, detached: !isWindows, windowsHide: isWindows, env: { From 9adad2f369bbeea7e7295614f3e3e5342d20ce5a Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Fri, 12 Dec 2025 14:02:50 +0100 Subject: [PATCH 2/3] Fix 1 test and silence CodeQL warning (not introduced by this PR) --- packages/cli/src/utils/sandbox.ts | 4 ++++ packages/core/src/services/shellExecutionService.test.ts | 8 +++++--- packages/core/src/services/shellExecutionService.ts | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index 193fb9370..2ba648dad 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -291,6 +291,8 @@ export async function start_sandbox( sandboxEnv['NO_PROXY'] = noProxy; sandboxEnv['no_proxy'] = noProxy; } + // CLI tool intentionally executes user-provided proxy commands + // codeql-disable-next-line js/shell-command-injection-from-environment proxyProcess = spawn('bash', ['-c', proxyCommand], { stdio: ['ignore', 'pipe', 'pipe'], detached: true, @@ -785,6 +787,8 @@ export async function start_sandbox( const proxyShellArgs = isWindows ? ['/c', proxyContainerCommand] : ['-c', proxyContainerCommand]; + // CLI tool intentionally executes user-provided proxy commands in container + // codeql-disable-next-line js/shell-command-injection-from-environment proxyProcess = spawn(proxyShell, proxyShellArgs, { stdio: ['ignore', 'pipe', 'pipe'], detached: true, diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index 6ee7ed45d..8c8e7bd4a 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -580,9 +580,11 @@ describe('ShellExecutionService child_process fallback', () => { }); expect(mockCpSpawn).toHaveBeenCalledWith( - 'ls -l', - [], - expect.objectContaining({ shell: 'bash' }), + 'bash', + ['-c', 'ls -l'], + expect.objectContaining({ + detached: true, + }), ); expect(result.exitCode).toBe(0); expect(result.signal).toBeNull(); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 22d8d3c62..603216a76 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -228,6 +228,8 @@ export class ShellExecutionService { ? ['/c', commandToExecute] : ['-c', commandToExecute]; + // CLI tool intentionally executes user-provided shell commands + // codeql-disable-next-line js/shell-command-injection-from-environment const child = cpSpawn(shell, shellArgs, { cwd, stdio: ['ignore', 'pipe', 'pipe'], From fa8b5a776252b8426d0e573df5071056848b5458 Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Fri, 12 Dec 2025 14:42:20 +0100 Subject: [PATCH 3/3] Replace the not functional CodeQL comments --- packages/cli/src/utils/sandbox.ts | 8 ++++---- packages/core/src/services/shellExecutionService.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index 2ba648dad..71f5c47d8 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -291,8 +291,8 @@ export async function start_sandbox( sandboxEnv['NO_PROXY'] = noProxy; sandboxEnv['no_proxy'] = noProxy; } - // CLI tool intentionally executes user-provided proxy commands - // codeql-disable-next-line js/shell-command-injection-from-environment + // Note: CodeQL flags this as js/shell-command-injection-from-environment. + // This is intentional - CLI tool executes user-provided proxy commands. proxyProcess = spawn('bash', ['-c', proxyCommand], { stdio: ['ignore', 'pipe', 'pipe'], detached: true, @@ -787,8 +787,8 @@ export async function start_sandbox( const proxyShellArgs = isWindows ? ['/c', proxyContainerCommand] : ['-c', proxyContainerCommand]; - // CLI tool intentionally executes user-provided proxy commands in container - // codeql-disable-next-line js/shell-command-injection-from-environment + // Note: CodeQL flags this as js/shell-command-injection-from-environment. + // This is intentional - CLI tool executes user-provided proxy commands in container. proxyProcess = spawn(proxyShell, proxyShellArgs, { stdio: ['ignore', 'pipe', 'pipe'], detached: true, diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 603216a76..3d812d899 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -228,8 +228,8 @@ export class ShellExecutionService { ? ['/c', commandToExecute] : ['-c', commandToExecute]; - // CLI tool intentionally executes user-provided shell commands - // codeql-disable-next-line js/shell-command-injection-from-environment + // Note: CodeQL flags this as js/shell-command-injection-from-environment. + // This is intentional - CLI tool executes user-provided shell commands. const child = cpSpawn(shell, shellArgs, { cwd, stdio: ['ignore', 'pipe', 'pipe'],