From e8cd4e7c6ea59e5ddd84085eb0250d7a6ee15e0f Mon Sep 17 00:00:00 2001 From: Automaker Date: Sun, 26 Apr 2026 16:16:06 -0700 Subject: [PATCH] fix(shell): replace polynomial-regex patterns with plain string ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeQL flagged two new alerts on the background-shell wrapper code landed in #140: - js/polynomial-redos at shell.ts:292 ('&+$' on a trimmed string) - js/polynomial-redos at shell.ts:305 ('\\s*&\\s*$' on a trimmed string) Both are low practical risk (the inputs are bounded model-emitted shell commands) but the alert is blocking the dev → main promotion in PR #141. Swap each for a plain string-op equivalent — same intent, no quantifier-on-quantifier shape for the analyzer to flag. Co-Authored-By: Claude Sonnet 4.6 --- packages/core/src/tools/shell.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index e63feb920..c2a7964fd 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -289,7 +289,12 @@ export class ShellToolInvocation extends BaseToolInvocation< // On Windows, we rely on the race logic below to handle background tasks. // We just ensure the command string is clean. if (isWindows && shouldRunInBackground) { - finalCommand = finalCommand.trim().replace(/&+$/, '').trim(); + // Strip any trailing & without a regex — `&+$` looked benign but + // tripped CodeQL's polynomial-redos rule. A simple loop has no + // backtracking risk and the same intent. + let s = finalCommand.trim(); + while (s.endsWith('&')) s = s.slice(0, -1); + finalCommand = s.trimEnd(); } // Build the shell wrapper. @@ -301,8 +306,11 @@ export class ShellToolInvocation extends BaseToolInvocation< // - For foreground commands: pass through unchanged. const commandToExecute = (() => { if (!shouldRunInBackground || !useDiskCapture) return finalCommand; - // Strip trailing & — we'll re-add it on the subshell wrapper. - const inner = finalCommand.trim().replace(/\s*&\s*$/, ''); + // Strip trailing & (and any preceding whitespace) — we'll re-add it + // on the subshell wrapper. Plain string ops instead of `\s*&\s*$` + // because the regex tripped CodeQL's polynomial-redos rule. + let inner = finalCommand.trim(); + if (inner.endsWith('&')) inner = inner.slice(0, -1).trimEnd(); return [ // () >output 2>&1; echo $? >exit — runs in its own subshell // detached with `&`, so the OS keeps writing even after our