From b4e69c68620a822407d45bfbba6ee10feebc70a3 Mon Sep 17 00:00:00 2001 From: Lucas Trzesniewski Date: Tue, 12 May 2026 23:32:56 +0200 Subject: [PATCH] Add `safeUriSchemes` setting (#20207) This adds a `safeUriSchemes` global setting which lets you define hyperlink URI schemes which the user considers safe. No confirmation dialog will be shown when trying to open hyperlinks which use these schemes. - This solves the root issue, but doesn't introduce any UI or documentation changes. I wanted to validate the approach and implementation with you first. - I closely followed the code handling the `disabledProfileSources` setting, which is of the same type. - This feature does not change the behavior of `http`, `https` and `file` schemes. Validation I ran the dev terminal, and tested the behavior by clicking on `vscode` hyperlinks generated by ripgrep with various `safeUriSchemes` settings: - Setting not defined - asks for confirmation - `["vscode"]` - does not ask for confirmation - `["foo", "vscode"]` - does not ask for confirmation - `["foo"]` - asks for confirmation - `null` - asks for confirmation - `[]` - asks for confirmation - `[""]` - asks for confirmation - `[{"foo": "bar"}]` - fails to deserialize (as expected) A few uinit tests failed, but they seem unrelated to these changes: - `KeyBindingTests` in `UnitTests_SettingsModel`, probably because I use an AZERTY keyboard. - A few `Conhost` tests, but I didn't touch this part Refs #20065 Closes #20191 (cherry picked from commit fb71a0462edaf32a7ac4a5ebb4df3bd05bacb41e) Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgshlaM Service-Version: 1.24 --- doc/cascadia/profiles.schema.json | 7 +++++++ src/cascadia/TerminalApp/TerminalPage.cpp | 18 +++++++++++++++--- src/cascadia/TerminalApp/TerminalPage.h | 2 +- .../GlobalAppSettings.cpp | 8 ++++++++ .../GlobalAppSettings.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 1 + .../SerializationTests.cpp | 1 + 7 files changed, 34 insertions(+), 4 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index be78d1c36c..1bb60a2374 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2462,6 +2462,13 @@ }, "type": "array" }, + "safeUriSchemes": { + "description": "Specifies a list of URI schemes that are considered safe. No confirmation will be required to open URIs with these schemes.", + "items": { + "type": "string" + }, + "type": "array" + }, "rendering.graphicsAPI": { "description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.", "type": "string", diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 658323384d..8597f1d28f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -3144,13 +3144,15 @@ namespace winrt::TerminalApp::implementation return true; } - bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) + bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const { - if (parsedUri.SchemeName() == L"http" || parsedUri.SchemeName() == L"https") + const auto& schemeName = parsedUri.SchemeName(); + + if (schemeName == L"http" || schemeName == L"https") { return true; } - if (parsedUri.SchemeName() == L"file") + if (schemeName == L"file") { static const auto pathext{ wil::TryGetEnvironmentVariableW(L"PATHEXT") }; const auto filename = parsedUri.Path(); @@ -3164,6 +3166,16 @@ namespace winrt::TerminalApp::implementation return true; } + if (const auto& safeSchemes = _settings.GlobalSettings().SafeUriSchemes()) + { + for (const auto& scheme : safeSchemes) + { + if (til::equals_insensitive_ascii(schemeName, scheme)) + { + return true; + } + } + } return false; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 7e7b6d1eba..5dc1ff5d0d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -418,7 +418,7 @@ namespace winrt::TerminalApp::implementation safe_void_coroutine _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs); static bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri); - static bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri); + bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const; void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri); bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index d7c0e5a6a7..db8bcf2d1f 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -102,6 +102,14 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_DisabledProfileSources->Append(src); } } + if (_SafeUriSchemes) + { + globals->_SafeUriSchemes = winrt::single_threaded_vector(); + for (const auto& src : *_SafeUriSchemes) + { + globals->_SafeUriSchemes->Append(src); + } + } for (const auto& parent : _parents) { diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index e003660f32..994299e5d7 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -105,6 +105,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, EnableUnfocusedAcrylic); INHERITABLE_SETTING(Boolean, AllowHeadless); INHERITABLE_SETTING(String, SearchWebDefaultQueryUrl); + INHERITABLE_SETTING(IVector, SafeUriSchemes); Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 9040f89cbf..69a2badc01 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -62,6 +62,7 @@ Author(s): X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \ X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \ X(winrt::Windows::Foundation::Collections::IVector, DisabledProfileSources, "disabledProfileSources", nullptr) \ + X(winrt::Windows::Foundation::Collections::IVector, SafeUriSchemes, "safeUriSchemes", nullptr) \ X(bool, ShowAdminShield, "showAdminShield", true) \ X(bool, TrimPaste, "trimPaste", true) \ X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \ diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 3112f83939..cbda46e9fb 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -461,6 +461,7 @@ namespace SettingsModelUnitTests "$schema" : "https://aka.ms/terminal-profiles-schema", "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", "disabledProfileSources": [ "Windows.Terminal.Wsl" ], + "safeUriSchemes": [ "vscode" ], "newTabMenu": [ {