Chrome · Browser
CVE-2026-87436
Logic Error in Browser
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
RegisterProtocolHandlerExtensionBrowserTestchrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc |
modified |
Files Changed
chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.ccchrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
Patch
From 4fccde21b042fd9b7487e630c08bda96a3407769 Mon Sep 17 00:00:00 2001 From: Javier Fernández García-Boente <[email protected]> Date: Sat, 15 Aug 2026 09:26:48 -0700 Subject: [PATCH] Reland "Custom handlers: tie extension-registered handlers to their extension" This is a reland of commit 6aaad9dc9d6d602d9415612ca5199e9fba068b8b Original change's description: > Custom handlers: tie extension-registered handlers to their extension > > A protocol handler at the kExtensionFeatures security level relaxes the > HTML-spec restrictions (allowing cross-origin target URLs and the > ext+/chrome-extension schemes). Extension cleanup -- uninstall/disable > handling and ProtocolHandlersSanityCheck -- keys off the handler's > extension_id, so a handler with the elevated level but no extension_id > can never be removed: its privileges survive the registering extension's > removal, and a build that predates recording the extension_id at > registration time could have persisted such an orphan to prefs. > > In this CL we associate such handlers with the registering extension: > > - In Browser, centralize handler construction in a new > CreateProtocolHandlerForFrame() helper. For a privileged extension > frame it builds the handler with the owning extension's id. > > - In ProtocolHandlersManager::OnExtensionUnloaded, remove every handler > returned by GetExtensionProtocolHandlers(extension->id()) instead of > only the manifest-declared ones. > > Additionally, a new method ProtocolHandler::IsAllowedExtensionHandler() > adds an extra security check which drops orphaned handlers on reload > rather than re-registering them with their elevated privileges intact. > > Add a browser test, HandlerRemovedOnExtensionUninstall, that registers a > handler from an extension page through the JS API and verifies it is > associated with the extension and removed on uninstall. > > Add also a regression test covering the orphan rejection and the pref > round-trip, and update the two scheme-validation tests that previously > registered untagged kExtensionFeatures handlers to use realistic > extension-tagged handlers. > > Bug: 511754574 > Change-Id: I404c00a7c69a06443c8ece8f75ebe9fbef065a48 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8103530 > Reviewed-by: Ted Choc <[email protected]> > Reviewed-by: Antonio Sartori <[email protected]> > Reviewed-by: Solomon Kinard <[email protected]> > Commit-Queue: Javier Fernandez <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1678351} Bug: 511754574 Change-Id: Ia77132d66c1b98e26fe986f9e574cd04a37e61df Include-Ci-Only-Tests: chromium.mac:mac26-arm64-rel-tests|browser_tests Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8259429 Reviewed-by: Solomon Kinard <[email protected]> Commit-Queue: Javier Fernandez <[email protected]> Reviewed-by: Antonio Sartori <[email protected]> Reviewed-by: Ted Choc <[email protected]> Cr-Commit-Position: refs/heads/main@{#1680194} --- diff --git a/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc index b02bbb4..a802795 100644 --- a/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc +++ b/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc @@ -18,6 +18,8 @@ protected: ChromeProtocolHandlerRegistryTest() = default; + // Extension handlers shouldn't use this method. See the comment in the + // definition of the ExtensionHandlerCanRegisterProtocol below. bool ProtocolHandlerCanRegisterProtocol( const std::string& protocol, const GURL& handler_url, @@ -28,6 +30,18 @@ return registry_->IsHandledProtocol(protocol); } + // Like above, but registers an extension handler (kExtensionFeatures plus the + // associated extension id). Extension handlers start unconfirmed, so success + // is checked via the handler list rather than IsHandledProtocol. + bool ExtensionHandlerCanRegisterProtocol(const std::string& protocol, + const GURL& handler_url, + const std::string& extension_id) { + registry_->OnAcceptRegisterProtocolHandler( + ProtocolHandler::CreateExtensionProtocolHandler(protocol, handler_url, + extension_id)); + return !registry_->GetHandlersFor(protocol).empty(); + } + void SetUp() override { profile_ = std::make_unique<TestingProfile>(); CHECK(profile_->GetPrefs()); @@ -64,8 +78,10 @@ "news", chrome_extension_handler_url, blink::ProtocolHandlerSecurityLevel::kUntrustedOrigins)); - EXPECT_TRUE(ProtocolHandlerCanRegisterProtocol( - "news", chrome_extension_handler_url, - blink::ProtocolHandlerSecurityLevel::kExtensionFeatures)); + // A chrome-extension:// target URL is only allowed at the kExtensionFeatures + // level, which is reserved for extension handlers, so register one via the + // extension factory (which supplies the required extension id). + EXPECT_TRUE(ExtensionHandlerCanRegisterProtocol( + "news", chrome_extension_handler_url, "extension_id")); } #endif // BUILDFLAG(ENABLE_EXTENSIONS_CORE) diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc index db29165..07bf17a8 100644 --- a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc +++ b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc @@ -286,8 +286,73 @@ ASSERT_EQ(0u, registry->GetHandlersFor(url.GetScheme()).size()); } -using RegisterProtocolHandlerExtensionBrowserTest = - extensions::ExtensionBrowserTest; +class RegisterProtocolHandlerExtensionBrowserTest + : public extensions::ExtensionBrowserTest { + public: + void SetUpOnMainThread() override { + extensions::ExtensionBrowserTest::SetUpOnMainThread(); + + // Disable OS-level registration, as + // ChromeRegisterProtocolHandlerBrowserTest does; see + // TestProtocolHandlerRegistryDelegate above. Without it these tests do a + // real OS round trip for every default they register, and its asynchronous + // reply clears the default handler again on the platforms where + // ShouldRemoveHandlersNotInOS() is true. + ProtocolHandlerRegistryFactory::GetForBrowserContext( + browser()->GetProfile()) + ->SetDelegateForTesting( + std::make_unique<TestProtocolHandlerRegistryDelegate>()); + } +}; + +// A handler registered from a privileged extension page via +// navigator.registerProtocolHandler must be associated with the registering +// extension and removed when that extension is uninstalled. +IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest, + HandlerRemovedOnExtensionUninstall) { +#if BUILDFLAG(IS_MAC) + ASSERT_TRUE(test::RegisterAppWithLaunchServices()); +#endif + permissions::PermissionRequestManager::FromWebContents( + browser()->tab_strip_model()->GetActiveWebContents()) + ->set_auto_response_for_test( + permissions::PermissionRequestManager::ACCEPT_ALL); + + const extensions::Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("protocol_handler")); + ASSERT_NE(nullptr, extension); + const std::string extension_id = extension->id(); + + ProtocolHandlerRegistry* registry = + ProtocolHandlerRegistryFactory::GetForBrowserContext( + browser()->GetProfile()); + + // Register a handler from the extension page via the JS API. The waiter must + // only cover the registration; see the comment in + // JsHandlerDoesNotOverrideNonExtensionDefault. + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), GURL("chrome-extension://" + extension_id + "/test.html"))); + { + ProtocolHandlerChangeWaiter waiter(registry); + ASSERT_TRUE(content::ExecJs( + browser()->tab_strip_model()->GetActiveWebContents(), + "navigator.registerProtocolHandler('geo', 'test.html?%s', 'test');")); + waiter.Wait(); + } + ASSERT_TRUE(registry->IsHandledProtocol("geo")); + + // The handler must be associated with the registering extension so that it + // is visible to extension cleanup. + ProtocolHandlerRegistry::ProtocolHandlerList extension_handlers = + registry->GetExtensionProtocolHandlers(extension_id); + ASSERT_EQ(1u, extension_handlers.size()); + EXPECT_EQ("geo", extension_handlers[0].protocol()); + + // Uninstalling the extension must remove the handler it registered. + UninstallExtension(extension_id); + EXPECT_FALSE(registry->IsHandledProtocol("geo")); + EXPECT_TRUE(registry->GetExtensionProtocolHandlers().empty()); +} IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest, Basic) { #if BUILDFLAG(IS_MAC) @@ -305,13 +370,14 @@ std::string handler_url = "chrome-extension://" + extension->id() + "/test.html"; - // Register the handler. + // Register the handler. The waiter must only cover the registration; see the + // comment in JsHandlerDoesNotOverrideNonExtensionDefault. + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(handler_url))); { ProtocolHandlerRegistry* registry = ProtocolHandlerRegistryFactory::GetForBrowserContext( browser()->GetProfile());
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc
index b02bbb4..a802795 100644
--- a/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc
+++ b/chrome/browser/custom_handlers/chrome_protocol_handler_registry_unittest.cc
@@ -18,6 +18,8 @@
protected:
ChromeProtocolHandlerRegistryTest() = default;
+ // Extension handlers shouldn't use this method. See the comment in the
+ // definition of the ExtensionHandlerCanRegisterProtocol below.
bool ProtocolHandlerCanRegisterProtocol(
const std::string& protocol,
const GURL& handler_url,
@@ -28,6 +30,18 @@
return registry_->IsHandledProtocol(protocol);
}
+ // Like above, but registers an extension handler (kExtensionFeatures plus the
+ // associated extension id). Extension handlers start unconfirmed, so success
+ // is checked via the handler list rather than IsHandledProtocol.
+ bool ExtensionHandlerCanRegisterProtocol(const std::string& protocol,
+ const GURL& handler_url,
+ const std::string& extension_id) {
+ registry_->OnAcceptRegisterProtocolHandler(
+ ProtocolHandler::CreateExtensionProtocolHandler(protocol, handler_url,
+ extension_id));
+ return !registry_->GetHandlersFor(protocol).empty();
+ }
+
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
CHECK(profile_->GetPrefs());
@@ -64,8 +78,10 @@
"news", chrome_extension_handler_url,
blink::ProtocolHandlerSecurityLevel::kUntrustedOrigins));
- EXPECT_TRUE(ProtocolHandlerCanRegisterProtocol(
- "news", chrome_extension_handler_url,
- blink::ProtocolHandlerSecurityLevel::kExtensionFeatures));
+ // A chrome-extension:// target URL is only allowed at the kExtensionFeatures
+ // level, which is reserved for extension handlers, so register one via the
+ // extension factory (which supplies the required extension id).
+ EXPECT_TRUE(ExtensionHandlerCanRegisterProtocol(
+ "news", chrome_extension_handler_url, "extension_id"));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
index db29165..07bf17a8 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
@@ -286,8 +286,73 @@
ASSERT_EQ(0u, registry->GetHandlersFor(url.GetScheme()).size());
}
-using RegisterProtocolHandlerExtensionBrowserTest =
- extensions::ExtensionBrowserTest;
+class RegisterProtocolHandlerExtensionBrowserTest
+ : public extensions::ExtensionBrowserTest {
+ public:
+ void SetUpOnMainThread() override {
+ extensions::ExtensionBrowserTest::SetUpOnMainThread();
+
+ // Disable OS-level registration, as
+ // ChromeRegisterProtocolHandlerBrowserTest does; see
+ // TestProtocolHandlerRegistryDelegate above. Without it these tests do a
+ // real OS round trip for every default they register, and its asynchronous
+ // reply clears the default handler again on the platforms where
+ // ShouldRemoveHandlersNotInOS() is true.
+ ProtocolHandlerRegistryFactory::GetForBrowserContext(
+ browser()->GetProfile())
+ ->SetDelegateForTesting(
+ std::make_unique<TestProtocolHandlerRegistryDelegate>());
+ }
+};
+
+// A handler registered from a privileged extension page via
+// navigator.registerProtocolHandler must be associated with the registering
+// extension and removed when that extension is uninstalled.
+IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest,
+ HandlerRemovedOnExtensionUninstall) {
+#if BUILDFLAG(IS_MAC)
+ ASSERT_TRUE(test::RegisterAppWithLaunchServices());
+#endif
+ permissions::PermissionRequestManager::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->set_auto_response_for_test(
+ permissions::PermissionRequestManager::ACCEPT_ALL);
+
+ const extensions::Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("protocol_handler"));
+ ASSERT_NE(nullptr, extension);
+ const std::string extension_id = extension->id();
+
+ ProtocolHandlerRegistry* registry =
+ ProtocolHandlerRegistryFactory::GetForBrowserContext(
+ browser()->GetProfile());
+
+ // Register a handler from the extension page via the JS API. The waiter must
+ // only cover the registration; see the comment in
+ // JsHandlerDoesNotOverrideNonExtensionDefault.
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), GURL("chrome-extension://" + extension_id + "/test.html")));
+ {
+ ProtocolHandlerChangeWaiter waiter(registry);
+ ASSERT_TRUE(content::ExecJs(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "navigator.registerProtocolHandler('geo', 'test.html?%s', 'test');"));
+ waiter.Wait();
+ }
+ ASSERT_TRUE(registry->IsHandledProtocol("geo"));
+
+ // The handler must be associated with the registering extension so that it
+ // is visible to extension cleanup.
+ ProtocolHandlerRegistry::ProtocolHandlerList extension_handlers =
+ registry->GetExtensionProtocolHandlers(extension_id);
+ ASSERT_EQ(1u, extension_handlers.size());
+ EXPECT_EQ("geo", extension_handlers[0].protocol());
+
+ // Uninstalling the extension must remove the handler it registered.
+ UninstallExtension(extension_id);
+ EXPECT_FALSE(registry->IsHandledProtocol("geo"));
+ EXPECT_TRUE(registry->GetExtensionProtocolHandlers().empty());
+}
IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest, Basic) {
#if BUILDFLAG(IS_MAC)
@@ -305,13 +370,14 @@
std::string handler_url =
"chrome-extension://" + extension->id() + "/test.html";
- // Register the handler.
+ // Register the handler. The waiter must only cover the registration; see the
+ // comment in JsHandlerDoesNotOverrideNonExtensionDefault.
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(handler_url)));
{
ProtocolHandlerRegistry* registry =
ProtocolHandlerRegistryFactory::GetForBrowserContext(
browser()->GetProfile());
ProtocolHandlerChangeWaiter waiter(registry);
- ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(handler_url)));
ASSERT_TRUE(content::ExecJs(
browser()->tab_strip_model()->GetActiveWebContents(),
"navigator.registerProtocolHandler('geo', 'test.html?%s', 'test');"));
@@ -326,6 +392,108 @@
->GetLastCommittedURL());
}
+// A handler registered from an extension page via
+// navigator.registerProtocolHandler uses the elevated kExtension security
+// level, so it must not take the default away from a pre-existing
+// non-extension default handler.
+IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest,
+ JsHandlerDoesNotOverrideNonExtensionDefault) {
+#if BUILDFLAG(IS_MAC)
+ ASSERT_TRUE(test::RegisterAppWithLaunchServices());
+#endif
+ permissions::PermissionRequestManager::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->set_auto_response_for_test(
+ permissions::PermissionRequestManager::ACCEPT_ALL);
+
+ ProtocolHandlerRegistry* registry =
+ ProtocolHandlerRegistryFactory::GetForBrowserContext(
+ browser()->GetProfile());
+
+ // A non-extension (e.g. WebAPI/PWA) handler is already the default for 'geo'.
+ ProtocolHandler non_extension_handler =
+ ProtocolHandler::CreateProtocolHandler(
+ "geo", GURL("https://non-extension.example/%s"));
+ registry->OnAcceptRegisterProtocolHandler(non_extension_handler);
+ ASSERT_TRUE(registry->IsDefault(non_extension_handler));
+
+ const extensions::Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("protocol_handler"));
+ ASSERT_NE(nullptr, extension);
+
+ // Register a 'geo' handler from the extension page via the JS API. The waiter
+ // only covers the registration itself: it quits on the first registry change,
+ // so arming it before the navigation would let an unrelated change satisfy it
+ // and return before the handler is stored.
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), GURL("chrome-extension://" + extension->id() + "/test.html")));
+ {
+ ProtocolHandlerChangeWaiter waiter(registry);
+ ASSERT_TRUE(content::ExecJs(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "navigator.registerProtocolHandler('geo', 'test.html?%s', 'test');"));
+ waiter.Wait();
+ }
+
+ // The extension handler is registered, but the non-extension handler must
+ // remain the default.
+ EXPECT_EQ(1u, registry->GetExtensionProtocolHandlers(extension->id()).size());
+ EXPECT_TRUE(registry->IsDefault(non_extension_handler));
+ EXPECT_FALSE(registry->GetHandlerFor("geo").IsExtensionHandler());
+}
+
+// When the pre-existing default is itself an extension handler, a handler
+// registered from an extension page via navigator.registerProtocolHandler may
+// become the default: the restriction is only against overriding a
+// non-extension handler.
+IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest,
+ JsHandlerOverridesExtensionDefault) {
+#if BUILDFLAG(IS_MAC)
+ ASSERT_TRUE(test::RegisterAppWithLaunchServices());
+#endif
+ permissions::PermissionRequestManager::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->set_auto_response_for_test(
+ permissions::PermissionRequestManager::ACCEPT_ALL);
+
+ ProtocolHandlerRegistry* registry =
+ ProtocolHandlerRegistryFactory::GetForBrowserContext(
+ browser()->GetProfile());
+
+ // Another extension's handler is already the default for 'geo'.
+ ProtocolHandler other_extension_handler =
+ ProtocolHandler::CreateExtensionProtocolHandler(
+ "geo", GURL("https://other-extension.example/%s"),
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ other_extension_handler.Confirm();
+ registry->OnAcceptRegisterProtocolHandler(other_extension_handler);
+ ASSERT_TRUE(registry->IsDefault(other_extension_handler));
+
+ const extensions::Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("protocol_handler"));
+ ASSERT_NE(nullptr, extension);
+
+ // Register a 'geo' handler from the extension page via the JS API. The waiter
+ // must only cover the registration; see the comment in
+ // JsHandlerDoesNotOverrideNonExtensionDefault.
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), GURL("chrome-extension://" + extension->id() + "/test.html")));
+ {
+ ProtocolHandlerChangeWaiter waiter(registry);
+ ASSERT_TRUE(content::ExecJs(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "navigator.registerProtocolHandler('geo', 'test.html?%s', 'test');"));
+ waiter.Wait();
+ }
+
+ // The newly registered extension handler overrides the previous extension
+ // default.
+ EXPECT_FALSE(registry->IsDefault(other_extension_handler));
+ const ProtocolHandler& new_default = registry->GetHandlerFor("geo");
+ ASSERT_TRUE(new_default.extension_id().has_value());
+ EXPECT_EQ(extension->id(), *new_default.extension_id());
+}
+
class ChromeRegisterProtocolHandlerAndServiceWorkerInterceptor
: public InProcessBrowserTest {
public:
diff --git a/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js b/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
index 2a18c2b..6ae1d29b 100644
--- a/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
+++ b/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
@@ -122,8 +122,14 @@
async function chromeExtensionURL() {
chrome.test.assertTrue(
SAME_ORIGIN_CHROME_EXTENSION_URL.startsWith('chrome-extension://'));
+ // Use a safelisted scheme with no predefined default handler. mailto and
+ // webcal must be avoided here: on Chrome OS they ship a predefined
+ // non-extension default handler, and an extension handler (kExtension
+ // security level) is not allowed to override a non-extension default, so
+ // the extension handler would never become the default and the
+ // navigation below would not resolve through it.
await testRegisterProtocolHandler(
- 'mailto', SAME_ORIGIN_CHROME_EXTENSION_URL, TITLE);
+ 'xmpp', SAME_ORIGIN_CHROME_EXTENSION_URL, TITLE);
chrome.test.succeed();
},
diff --git a/components/custom_handlers/protocol_handler_registry_unittest.cc b/components/custom_handlers/protocol_handler_registry_unittest.cc
index f186896..552783c 100644
--- a/components/custom_handlers/protocol_handler_registry_unittest.cc
+++ b/components/custom_handlers/protocol_handler_registry_unittest.cc
@@ -342,6 +342,48 @@
EXPECT_EQ(now, recreated.last_modified());
}
+// A handler at the kExtensionFeatures security level with no extension_id is an
+// orphan: extension cleanup keys off the extension_id, so such a handler can
+// never be removed on uninstall/disable. Builds from before the extension_id
+// was populated at registration time could persist one to prefs, where its
+// elevated level would keep validating a cross-origin URL indefinitely.
+// IsAllowedExtensionHandler() must reject it so it is dropped when reloaded
+// rather than silently re-registered with its elevated privileges intact.
+TEST_F(ProtocolHandlerRegistryTest,
+ ExtensionFeaturesHandlerWithoutExtensionIdIsRejected) {
+ base::Time now = base::Time::Now();
+ const GURL cross_origin_url("https://attacker.example/steal?url=%s");
+
+ ProtocolHandler orphan(
+ "mailto", cross_origin_url, /*app_id=*/std::nullopt,
+ /*extension_id=*/std::nullopt, now,
+ /*is_confirmed=*/true, /*is_allowed_in_incognito=*/false,
+ blink::ProtocolHandlerSecurityLevel::kExtensionFeatures);
+ // The orphan is well-formed per the HTML spec (safelisted scheme, trustworthy
+ // URL); it is the missing extension association -- a concern separate from
+ // IsValid() -- that must disqualify it.
+ EXPECT_TRUE(orphan.IsValid());
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page