From 61bc54af0504d21bdf1e6a7e29dcf9ef322b89c7 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 5 Feb 2026 10:45:07 -0500 Subject: [PATCH] fix(shell): escape ! for cmd.exe delayed expansion Adds defensive escaping for exclamation marks in case delayed expansion is enabled in the user's cmd.exe environment. --- src/utils/shell-executor.ts | 1 + tests/unit/utils/shell-executor.test.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/utils/shell-executor.ts b/src/utils/shell-executor.ts index c6bf8359..a3206d6d 100644 --- a/src/utils/shell-executor.ts +++ b/src/utils/shell-executor.ts @@ -28,6 +28,7 @@ export function escapeShellArg(arg: string): string { .replace(/[\r\n\t]/g, ' ') // Replace newlines/tabs with space .replace(/%/g, '%%') // Escape percent signs .replace(/\^/g, '^^') // Escape carets + .replace(/!/g, '^^!') // Escape exclamation marks (delayed expansion) .replace(/"/g, '""') + // Escape quotes '"' ); diff --git a/tests/unit/utils/shell-executor.test.ts b/tests/unit/utils/shell-executor.test.ts index 8dbe930e..cca6f0cb 100644 --- a/tests/unit/utils/shell-executor.test.ts +++ b/tests/unit/utils/shell-executor.test.ts @@ -75,5 +75,10 @@ describe('escapeShellArg', () => { const { escapeShellArg } = await import('../../../src/utils/shell-executor'); expect(escapeShellArg('C:\\Program Files\\App')).toBe('"C:\\Program Files\\App"'); }); + + it('escapes exclamation marks for delayed expansion', async () => { + const { escapeShellArg } = await import('../../../src/utils/shell-executor'); + expect(escapeShellArg('hello!')).toBe('"hello^^!"'); + }); }); });