Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in ServiceWorker
DescriptionIncorrect authorization in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Tracker517774971
Fix commite30c3969db33 (chromium/src) +46/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • chrome/browser/chrome_content_browser_client.cc
  • chrome/browser/chrome_content_browser_client_unittest.cc
From e30c3969db337dad9ee43af0b85265e3afdb7b66 Mon Sep 17 00:00:00 2001
From: Shunya Shishido <[email protected]>
Date: Fri, 03 Jul 2026 02:12:27 -0700
Subject: [PATCH] Restrict ServiceWorker synthetic response to the DSE origin

IsServiceWorkerSyntheticResponseAllowed() gated on
IsDefaultSearchEngine(), which matches the default search provider's
alternate_urls in addition to its primary search URL. A search provider
that lists an alternate_url on a different origin therefore made
navigations to that origin eligible for the synthetic ServiceWorker
registration, which is returned ahead of the storage lookup for the
origin's own registration.

Require the navigated URL to be same-origin with the default search
provider's primary search URL so the synthetic registration is only used
on the search provider's own origin.

Update the existing unit test to use an https search URL so its positive
expectation holds under the new origin check, and add coverage for
alternate_urls on a different origin.

Bug: 517774971

TAG=agy

CONV=e3a74e33-5c1c-48e7-b139-14c8e9116f8f

Change-Id: I5fb614e0f8a60c734c4ff4ed314f330457696e7f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8028583
Reviewed-by: Yoshisato Yanagisawa <[email protected]>
Commit-Queue: Shunya Shishido <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1656443}
---

diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ee1657fe1..aeea3c7 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3844,13 +3844,22 @@
     return false;
   }
 
-  // Prewarm page can be treated as a DSE. As we don't want to enable synthetic
-  // response on the prewarm page, manually exclude it.
   auto* template_url_service =
       TemplateURLServiceFactory::GetForProfile(profile);
   CHECK(template_url_service);
-  if (IsPrewarmUrl(url,
-                   template_url_service->GetDefaultSearchProviderOrigin())) {
+  const url::Origin dse_origin =
+      template_url_service->GetDefaultSearchProviderOrigin();
+
+  // The synthetic registration is created for `url`'s origin. Restrict it to
+  // the default search provider's own origin so that alternate URLs on other
+  // origins don't get a synthetic registration.
+  if (!dse_origin.IsSameOriginWith(url)) {
+    return false;
+  }
+
+  // Prewarm page can be treated as a DSE. As we don't want to enable synthetic
+  // response on the prewarm page, manually exclude it.
+  if (IsPrewarmUrl(url, dse_origin)) {
     return false;
   }
 
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index e0d29c2..99ea1d9 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -651,7 +651,7 @@
   search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
   TemplateURLData data;
   data.SetShortName(u"example.com");
-  data.SetURL("http://example.com/test?q={searchTerms}");
+  data.SetURL("https://example.com/test?q={searchTerms}");
   data.new_tab_url = chrome::kChromeUINewTabURL;
   TemplateURL* template_url =
       template_url_service->Add(std::make_unique<TemplateURL>(data));
@@ -669,6 +669,38 @@
       profile(), GURL("https://example.com/test?q=test")));
 }
 
+TEST_F(ChromeContentBrowserClientTestWithWebContents,
+       IsServiceWorkerSyntheticResponseAllowedForAlternateUrls) {
+  ChromeContentBrowserClient browser_client;
+
+  // Update the default search engine with an alternate URL on a different
+  // origin.
+  TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+      profile(),
+      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+  TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
+  TemplateURLData data;
+  data.SetShortName(u"example.com");
+  data.SetURL("https://example.com/test?q={searchTerms}");
+  data.alternate_urls.push_back("https://other.test/{searchTerms}");
+  data.new_tab_url = chrome::kChromeUINewTabURL;
+  TemplateURL* template_url =
+      template_url_service->Add(std::make_unique<TemplateURL>(data));
+  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
+
+  // The synthetic response should only be allowed for navigations to the
+  // default search provider's own origin, even when an alternate URL on a
+  // different origin matches.
+  EXPECT_TRUE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("https://example.com/test?q=test")));
+  EXPECT_FALSE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("https://other.test/page")));
+  EXPECT_FALSE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("http://example.com/test?q=test")));
+}
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 // NOTE: Any updates to the expectations in these tests should also be done in
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index e0d29c2..99ea1d9 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -651,7 +651,7 @@
   search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
   TemplateURLData data;
   data.SetShortName(u"example.com");
-  data.SetURL("http://example.com/test?q={searchTerms}");
+  data.SetURL("https://example.com/test?q={searchTerms}");
   data.new_tab_url = chrome::kChromeUINewTabURL;
   TemplateURL* template_url =
       template_url_service->Add(std::make_unique<TemplateURL>(data));
@@ -669,6 +669,38 @@
       profile(), GURL("https://example.com/test?q=test")));
 }
 
+TEST_F(ChromeContentBrowserClientTestWithWebContents,
+       IsServiceWorkerSyntheticResponseAllowedForAlternateUrls) {
+  ChromeContentBrowserClient browser_client;
+
+  // Update the default search engine with an alternate URL on a different
+  // origin.
+  TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+      profile(),
+      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+  TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
+  TemplateURLData data;
+  data.SetShortName(u"example.com");
+  data.SetURL("https://example.com/test?q={searchTerms}");
+  data.alternate_urls.push_back("https://other.test/{searchTerms}");
+  data.new_tab_url = chrome::kChromeUINewTabURL;
+  TemplateURL* template_url =
+      template_url_service->Add(std::make_unique<TemplateURL>(data));
+  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
+
+  // The synthetic response should only be allowed for navigations to the
+  // default search provider's own origin, even when an alternate URL on a
+  // different origin matches.
+  EXPECT_TRUE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("https://example.com/test?q=test")));
+  EXPECT_FALSE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("https://other.test/page")));
+  EXPECT_FALSE(browser_client.IsServiceWorkerSyntheticResponseAllowed(
+      profile(), GURL("http://example.com/test?q=test")));
+}
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 // NOTE: Any updates to the expectations in these tests should also be done in
Loading diff…

Original Bug Report

reported by [email protected]

Extension Permission Bypass via alternate_urls Disabling Third-Party ServiceWorkers

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logical flaw in Chrome’s Settings Overrides API allows a malicious extension with only the chrome_settings_overrides permission to register arbitrary third-party origins under alternate_urls. When a user navigates to those origins, the browser mistakenly identifies them as eligible for a synthetic ServiceWorker response. This completely shadows the victim origin’s real ServiceWorker, silently bypassing its fetch interception and security controls.

Affected files:

  • content/browser/service_worker/service_worker_registry.cc
  • chrome/browser/chrome_content_browser_client.cc
  • chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc
  • chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
  • content/browser/service_worker/service_worker_loader_helpers.cc
  • content/browser/service_worker/service_worker_controllee_request_handler.cc

Estimated timestamp from git blame: 2025-05-26

Description

An extension with only the chrome_settings_overrides permission (and no host permissions) can register arbitrary third-party origins under search_provider.alternate_urls. When a user navigates to a listed URL, the browser’s synthetic-response matching flags it as a default search engine URL, prompting ServiceWorkerRegistry::FindRegistrationForClientUrl to early-return an in-memory synthetic ServiceWorker registration. This completely shadows the victim site’s real ServiceWorker, silently disabling its fetch interception, offline caching, and any client-side security policies or headers normally injected via the ServiceWorker.

Root Cause Analysis

  1. Unchecked alternate_urls Ingestion: In chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc (lines 131-138) and chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc (lines 70-112), the alternate_urls provided in the chrome_settings_overrides.search_provider manifest key are copied verbatim into TemplateURLData with no host, origin, or pattern validation.

  2. Attacker-Controlled Synthetic Response Eligibility: ChromeContentBrowserClient::IsServiceWorkerSyntheticResponseAllowed (defined in chrome/browser/chrome_content_browser_client.cc:3820) delegates its validation to IsDefaultSearchEngine, which resolves to true for any URL whose host and path matches any entry in alternate_urls via TemplateURL::IsSearchURL and TemplateURLRef::ExtractSearchTermsFromURL (defined in components/search_engines/template_url.cc).

  3. Registry Shadowing: In ServiceWorkerRegistry::FindRegistrationForClientUrl (content/browser/service_worker/service_worker_registry.cc line 428), synthetic response eligibility is evaluated before performing the storage lookup for the site’s real ServiceWorker registration. If eligible, it returns a synthetic ServiceWorker registration configured with fetch_handler_type = kNoHandler and router rules that route everything directly to the network. The database lookup for the real ServiceWorker is completely bypassed.

  4. Self-Reinforcing Cache Invalidation: When the synthetic branch runs with scopes == std::nullopt, DidFindRegistrationForClientUrl invokes ClearInternalCacheForStorageKey(key), wiping the in-memory scope-cache for the victim origin. This ensures subsequent navigations continue to bypass the real ServiceWorker.

Potential Attack Scenario

Note: These are potential steps. Our security review was performed statically, and we have not run a live proof of concept.

  1. An extension is installed whose manifest declares a chrome_settings_overrides block targeting an attacker’s search provider, and includes "alternate_urls": ["https://victim.com/{searchTerms}"] with no host permissions. The user accepts the “Change your search settings” prompt.
  2. The user navigates to https://victim.com/anything (a top-level GET request).
  3. Instead of running the victim’s legitimate registered ServiceWorker, Chrome serves a synthetic ServiceWorker configured with kNoHandler and network router rules. The page loads directly from the network, disabling any fetch interception, custom headers, or security policies (e.g. CSRF or CSP) deployed via the victim’s ServiceWorker.

Feature Flag Dependencies

This potential issue requires the ServiceWorkerSyntheticResponse feature to be enabled. It is currently disabled by default but active in field trials on platforms including Android, ChromeOS, Linux, macOS, and Windows via testing/variations/fieldtrial_testing_config.json.

Suggested Fix

Restrict alternate_urls parsing or registration in chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc and chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc to ensure that an extension cannot specify alternate search URLs targeting domains for which it does not hold host permissions. Alternatively, restrict ChromeContentBrowserClient::IsServiceWorkerSyntheticResponseAllowed to exclude alternate search URLs from synthetic response matching unless the origin is validated.

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker