diff --git a/src/main/pty/legacy-terminal-shim-dir.test.ts b/src/main/pty/legacy-terminal-shim-dir.test.ts index a604fd0a501..4fcd8c040a3 100644 --- a/src/main/pty/legacy-terminal-shim-dir.test.ts +++ b/src/main/pty/legacy-terminal-shim-dir.test.ts @@ -140,7 +140,7 @@ describe('legacy terminal shim neutralization', () => { const cmd = readFileSync(join(win32Dir, `${command}.cmd`), 'utf8') // No where.exe at all: it searches cwd first, so the wrapper walks the cleaned PATH. expect(cmd).not.toContain('where.exe') - expect(cmd).toContain('for %%P in ("%orca_clean_path:;=" "%") do call :orca_try_candidate') + expect(cmd).toContain('for %%P in ("%orca_clean_path:;=" "%") do (') expect(cmd).toContain(`if exist "%orca_candidate_dir%\\${command}.exe"`) // Why: %~f preserves a trailing separator; without normalizing, a wrapper-dir entry // spelled with one escapes self-exclusion and the wrapper tail-loops on itself. The @@ -149,11 +149,20 @@ describe('legacy terminal shim neutralization', () => { expect(cmd).toContain('set "orca_sep=\\"') // Why: a captured ORCA_REAL_* may be relative, and both the existence test and the // invocation resolve it against the cwd. - expect(cmd).toContain('if defined orca_real call :orca_check_rooted "%orca_real%"') + expect(cmd).toContain('if defined orca_real call :orca_check_rooted') expect(cmd).toContain('if defined orca_real if not defined orca_rooted set "orca_real="') // Why: the exported PATH is inherited by the real command; a relative entry left in it lets // the cwd select the tools that command spawns. expectOrdered(cmd, ':orca_append_path', 'if not defined orca_rooted exit /b') + // Why: CALL re-expands its own command line, so path data passed as an argument gets a + // second round of percent expansion. A PATH entry holding a literal %CD% became the + // current directory before the rooted check saw it and the planted git.cmd ran (rc=66 on + // Windows 11). Every caller must hand the value over in a variable instead. + expect(cmd).not.toContain('call :orca_check_rooted "') + expect(cmd).not.toContain('call :orca_try_candidate "') + expect(cmd).not.toContain('call :orca_append_path "') + expect(cmd).not.toContain('%~1') + expect(cmd).toContain('set "orca_probe=%orca_entry%"\r\ncall :orca_check_rooted\r\n') // Why: orca_sep is compared against before the legacy dir is normalized; defining it later // made that strip silently no-op and left the legacy dir on PATH. expectOrdered(cmd, 'set "orca_sep=', 'if "%orca_legacy_norm:~-1%."') @@ -162,7 +171,11 @@ describe('legacy terminal shim neutralization', () => { expect(cmd).not.toContain('=="%orca_sep%"') // Why: nothing else asserts the *reject* path, so deleting it would reopen the cwd hijack // while every accept-path assertion stayed green. - expectOrdered(cmd, ':orca_try_candidate', 'call :orca_check_rooted "%~1"') + expectOrdered( + cmd, + 'call :orca_check_rooted\r\nif not defined orca_rooted exit /b', + 'for %%G in ("%orca_entry%") do set "orca_candidate_dir=' + ) expect(cmd).toContain('if not defined orca_rooted exit /b') expect(cmd).toContain('if "%orca_candidate_dir:~-1%."=="%orca_sep%."') expect(cmd).not.toContain(':\\#=#%') @@ -177,7 +190,7 @@ describe('legacy terminal shim neutralization', () => { expect(cmd).toContain('if "%orca_probe:~0,2%"=="\\\\" set "orca_rooted=1"') // Why: these two guards are the only thing stopping an empty element reaching the cwd on // Windows; deleting either left every other assertion green. - expect(cmd).toContain('if "%~1"=="" exit /b') + expect(cmd).toContain('if not defined orca_entry exit /b') const powershell = readFileSync(join(win32Dir, `${command}-wrapper.ps1`), 'utf8') expect(powershell).not.toContain('Get-Command') @@ -571,7 +584,7 @@ describe('legacy terminal shim neutralization', () => { const cmd = readFileSync(join(win32Dir, 'git.cmd'), 'utf8') const cmdCapture = 'set "orca_legacy_wrapper_dir=%ORCA_ATTRIBUTION_SHIM_DIR%"' expectOrdered(cmd, cmdCapture, 'set "ORCA_ATTRIBUTION_SHIM_DIR="') - expect(cmd).toContain('for %%P in ("%PATH:;=" "%") do call :orca_append_path "%%~P"') + expect(cmd).toContain('for %%P in ("%PATH:;=" "%") do (') expect(cmd).toContain('if /I "%orca_path_entry_dir%"=="%orca_wrapper_dir%" exit /b') expect(cmd).toContain('if defined orca_legacy_wrapper_dir call :orca_reject_legacy_dir') // Why: `call :label && ...` is not valid cmd; the flag variable is what makes it work. diff --git a/src/main/pty/legacy-terminal-windows-tombstone.ts b/src/main/pty/legacy-terminal-windows-tombstone.ts index cddb84d8b59..7847605114e 100644 --- a/src/main/pty/legacy-terminal-windows-tombstone.ts +++ b/src/main/pty/legacy-terminal-windows-tombstone.ts @@ -26,7 +26,13 @@ if defined orca_legacy_wrapper_dir call :orca_normalize_legacy_dir rem Why: an empty PATH leaves the substitution below with an unbalanced quote, which rem desynchronizes cmd parsing for the rest of the file. Skip the line entirely instead. if not defined PATH goto :orca_path_walked -for %%P in ("%PATH:;=" "%") do call :orca_append_path "%%~P" +rem Why the variable: CALL re-expands its own command line, so a PATH entry holding a literal +rem %CD% would be turned into the current directory before the rooted check below ever sees it, +rem and the cwd would then be searched for __ORCA_COMMAND__. Proven on Windows 11. +for %%P in ("%PATH:;=" "%") do ( + set "orca_entry=%%~P" + call :orca_append_path +) :orca_path_walked set "PATH=%orca_clean_path%" set "ORCA_ENABLE_GIT_ATTRIBUTION=" @@ -40,7 +46,8 @@ set "ORCA_REAL_GH=" if defined orca_real for %%G in ("%orca_real%") do if /I "%%~dpG"=="%~dp0" set "orca_real=" rem Why: a captured path may be relative, and both "if exist" and the invocation resolve it rem against the current directory, so an inherited .\__ORCA_COMMAND__.exe would run from the repo. -if defined orca_real call :orca_check_rooted "%orca_real%" +set "orca_probe=%orca_real%" +if defined orca_real call :orca_check_rooted if defined orca_real if not defined orca_rooted set "orca_real=" rem Why: clear a captured path that no longer exists, or the PATH walk below is skipped. if defined orca_real if not exist "%orca_real%" set "orca_real=" @@ -48,7 +55,10 @@ if defined orca_real goto run rem Why: an unqualified Windows command lookup searches the current directory before PATH, so a rem repository-local __ORCA_COMMAND__.exe would win. Walk the cleaned PATH ourselves instead. if not defined orca_clean_path goto :orca_candidates_walked -for %%P in ("%orca_clean_path:;=" "%") do call :orca_try_candidate "%%~P" +for %%P in ("%orca_clean_path:;=" "%") do ( + set "orca_entry=%%~P" + call :orca_try_candidate +) :orca_candidates_walked if not defined orca_real ( echo Orca compatibility wrapper could not locate __ORCA_COMMAND__ on PATH. 1>&2 @@ -67,8 +77,10 @@ exit /b :orca_check_rooted rem Why: tested in pure batch on purpose. An external tool invoked here would itself be resolved rem from the current directory, reintroducing the very hijack this guard exists to prevent. +rem Why orca_probe rather than an argument: see the CALL re-expansion note above. set "orca_rooted=" -set "orca_probe=%~1" +rem Why: an unset probe would leave the substring syntax below unexpanded and mangle the line. +if not defined orca_probe exit /b if "%orca_probe:~0,2%"=="\\" set "orca_rooted=1" if "%orca_probe:~1,2%"==":\" set "orca_rooted=1" if "%orca_probe:~1,2%"==":/" set "orca_rooted=1" @@ -76,11 +88,12 @@ exit /b :orca_try_candidate if defined orca_real exit /b -if "%~1"=="" exit /b +if not defined orca_entry exit /b rem Why: a relative entry resolves against the current directory, same exposure as an empty one. -call :orca_check_rooted "%~1" +set "orca_probe=%orca_entry%" +call :orca_check_rooted if not defined orca_rooted exit /b -for %%G in ("%~1") do set "orca_candidate_dir=%%~fG" +for %%G in ("%orca_entry%") do set "orca_candidate_dir=%%~fG" rem Why: full-path expansion preserves a trailing separator, so without normalizing, the rem self-exclusion below misses a wrapper-dir entry spelled with one and the wrapper resolves rem to itself, looping forever. @@ -94,12 +107,13 @@ if not defined orca_real if exist "%orca_candidate_dir%\__ORCA_COMMAND__.bat" se exit /b :orca_append_path -if "%~1"=="" exit /b +if not defined orca_entry exit /b rem Why: the exported PATH is inherited by the real git and anything it spawns, so a relative rem entry left here lets the current directory select those tools instead. -call :orca_check_rooted "%~1" +set "orca_probe=%orca_entry%" +call :orca_check_rooted if not defined orca_rooted exit /b -for %%G in ("%~1") do set "orca_path_entry_dir=%%~fG" +for %%G in ("%orca_entry%") do set "orca_path_entry_dir=%%~fG" rem Why: full-path expansion preserves a trailing separator; normalize before comparing. if "%orca_path_entry_dir:~-1%."=="%orca_sep%." set "orca_path_entry_dir=%orca_path_entry_dir:~0,-1%" set "orca_path_entry_dir=%orca_path_entry_dir%\" @@ -107,7 +121,7 @@ if /I "%orca_path_entry_dir%"=="%orca_wrapper_dir%" exit /b set "orca_skip_entry=" if defined orca_legacy_wrapper_dir call :orca_reject_legacy_dir if defined orca_skip_entry exit /b -if defined orca_clean_path (set "orca_clean_path=%orca_clean_path%;%~1") else set "orca_clean_path=%~1" +if defined orca_clean_path (set "orca_clean_path=%orca_clean_path%;%orca_entry%") else set "orca_clean_path=%orca_entry%" exit /b :orca_reject_legacy_dir