From dafb04fb623fb6a4eb00384cc8aacf7bb3ccff5b Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Tue, 29 Sep 2026 17:14:35 -0700 Subject: [PATCH] Only open File Explorer from OpenCWD when CWD is a real directory (#20697) OpenCWD now no-ops unless the terminal working directory is an existing directory, then opens that folder. It no longer passes the raw OSC string to `explorer.exe` as parameters. Refs #12859 (the original action asked to do nothing when no CWD is set) OpenCWD was introduced in #18013. `ControlCore::OpenCWD` is the command-palette action `Terminal.OpenCWD`. It used: ```cpp ShellExecute(nullptr, nullptr, L"explorer", workingDirectory.c_str(), nullptr, SW_SHOW); ``` The working-directory string comes from OSC 7 (`file:` URI, then `PathCreateFromUrlW`) or OSC 9;9. Both only require `til::is_legal_path`, which allows `:` and `/`. So a remote host can set CWD to `https://example.com`, `shell:...`, or `/select,...`. Invoking Open CWD then ran Explorer with that text as `lpParameters`. Duplicate-tab and related paths already call `Utils::IsValidDirectory` before trusting this string (`TerminalPage.cpp`, `TerminalPaneContent.cpp`). Open CWD now does the same, then `ShellExecute`s the directory path as `lpFile` so Explorer switches cannot be supplied as parameters. If the CWD is empty or not a directory, the action does nothing. (cherry picked from commit 4e2b8bd9264642475d1398c11a5fd2aba06f373e) Service-Card-Id: PVTI_lADOAF3p4s4BlIihzg9jO10 Service-Version: 1.26 --- src/cascadia/TerminalControl/ControlCore.cpp | 6 +++++- src/types/ut_types/UtilsTests.cpp | 21 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7def807daf..9ca869bc7e 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1745,7 +1745,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::OpenCWD() { const auto workingDirectory = WorkingDirectory(); - ShellExecute(nullptr, nullptr, L"explorer", workingDirectory.c_str(), nullptr, SW_SHOW); + if (!Utils::IsValidDirectory(workingDirectory.c_str())) + { + return; + } + ShellExecute(nullptr, nullptr, workingDirectory.c_str(), nullptr, nullptr, SW_SHOW); } void ControlCore::ClearQuickFix() diff --git a/src/types/ut_types/UtilsTests.cpp b/src/types/ut_types/UtilsTests.cpp index fb83f0e875..01b4aab82c 100644 --- a/src/types/ut_types/UtilsTests.cpp +++ b/src/types/ut_types/UtilsTests.cpp @@ -34,6 +34,7 @@ class UtilsTests TEST_METHOD(TestDontTrimTrailingWhitespace); TEST_METHOD(TestEvaluateStartingDirectory); + TEST_METHOD(TestIsValidDirectory); void _VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue); void _VerifyXTermColorInvalid(const std::wstring_view wstr); @@ -618,3 +619,23 @@ void UtilsTests::TestEvaluateStartingDirectory() test(L"/dev", cwd, L"/dev"); } } + +void UtilsTests::TestIsValidDirectory() +{ + VERIFY_IS_FALSE(IsValidDirectory(nullptr)); + VERIFY_IS_FALSE(IsValidDirectory(L"")); + + // OSC 7 / 9;9 can store these. They are not directories. + VERIFY_IS_FALSE(IsValidDirectory(L"https://example.com")); + VERIFY_IS_FALSE(IsValidDirectory(L"/select,C:\\Windows")); + VERIFY_IS_FALSE(IsValidDirectory(L"shell:AppsFolder\\Foo")); + + wchar_t tempPath[MAX_PATH]{}; + VERIFY_IS_GREATER_THAN(GetTempPathW(ARRAYSIZE(tempPath), tempPath), 0u); + VERIFY_IS_TRUE(IsValidDirectory(tempPath)); + + wchar_t tempFile[MAX_PATH]{}; + VERIFY_ARE_NOT_EQUAL(0u, GetTempFileNameW(tempPath, L"ut", 0, tempFile)); + VERIFY_IS_FALSE(IsValidDirectory(tempFile)); + DeleteFileW(tempFile); +}