diff --git a/lib/hooks/websearch-transformer.cjs b/lib/hooks/websearch-transformer.cjs index 6da24c3e..eb198cba 100644 --- a/lib/hooks/websearch-transformer.cjs +++ b/lib/hooks/websearch-transformer.cjs @@ -874,7 +874,10 @@ function runGeminiCommand(args, timeoutMs) { timeout: timeoutMs, maxBuffer: 1024 * 1024 * 2, stdio: ['pipe', 'pipe', 'pipe'], - shell: isWindows, + // Never route query-derived prompts through a shell. Node concatenates + // arguments for shell-backed Windows spawns, which lets shell metacharacters + // in WebSearch queries escape the intended CLI invocation. + shell: false, }); if (result.error) { @@ -929,7 +932,10 @@ function tryOpenCodeSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) { timeout: timeoutSec * 1000, maxBuffer: 1024 * 1024 * 2, stdio: ['pipe', 'pipe', 'pipe'], - shell: isWindows, + // Never route query-derived prompts through a shell. Node concatenates + // arguments for shell-backed Windows spawns, which lets shell metacharacters + // in WebSearch queries escape the intended CLI invocation. + shell: false, } ); @@ -965,7 +971,10 @@ function tryGrokSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) { timeout: timeoutSec * 1000, maxBuffer: 1024 * 1024 * 2, stdio: ['pipe', 'pipe', 'pipe'], - shell: isWindows, + // Never route query-derived prompts through a shell. Node concatenates + // arguments for shell-backed Windows spawns, which lets shell metacharacters + // in WebSearch queries escape the intended CLI invocation. + shell: false, }); if (result.error) { diff --git a/tests/unit/hooks/websearch-transformer.test.ts b/tests/unit/hooks/websearch-transformer.test.ts index 646d1530..f05f9765 100644 --- a/tests/unit/hooks/websearch-transformer.test.ts +++ b/tests/unit/hooks/websearch-transformer.test.ts @@ -131,6 +131,15 @@ function runHookWithMockedFetch(mode: 'success' | 'empty' | 'non-result' | 'fail } } +describe('websearch-transformer legacy CLI safety', () => { + it('does not enable shell execution for query-derived legacy CLI prompts', () => { + const source = readFileSync(hookPath, 'utf8'); + + expect(source).not.toContain('shell: isWindows'); + expect(source.match(/shell: false/g) || []).toHaveLength(3); + }); +}); + describe('websearch-transformer hook helpers', () => { it('parses Retry-After seconds and HTTP dates', () => { expect(hook.parseRetryAfterSeconds('2')).toBe(2);