Medium watch CVSS 4.3 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in ServiceWorker
DescriptionIncorrect authorization in ServiceWorker
ComponentServiceWorker
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker517774971
Fix commite30c3969db33 (chromium/src) +46/-5
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-29 (per vendor advisory)
Fix landed2026-07-03 (public git)
Disclosed2026-08-25
Patch gap53 days — fix public in source before the advisory

Background

ServiceWorker synthetic response
a browser-side optimization that returns a synthetic Service Worker registration for the default search engine (DSE) so a navigation can be served ahead of the normal storage lookup for the origin’s own registration.
`IsServiceWorkerSyntheticResponseAllowed`
the ChromeContentBrowserClient gate in chrome_content_browser_client.cc that decides whether the synthetic registration is eligible for a given navigation URL.
`alternate_urls`
a TemplateURLData field listing additional URL patterns (possibly on other origins) that a search provider also treats as its own search endpoints.
`GetDefaultSearchProviderOrigin` / `IsSameOriginWith`
the DSE’s primary search-URL origin and the url::Origin same-origin comparison used to constrain the gate.

Root Cause Analysis

The eligibility gate ultimately depended on default-search-provider matching, which in the TemplateURLService matches not only the provider’s primary search URL but also every entry in its alternate_urls. Because alternate_urls can name a completely different origin, a navigation to that foreign origin was treated as a DSE navigation and made eligible for the synthetic Service Worker registration. That synthetic registration is created for url’s own origin and is returned ahead of the storage lookup for that origin’s real registration, so navigations to an unrelated origin could be served the search provider’s synthetic worker context — an authorization boundary violation across origins.

The fix computes dse_origin from GetDefaultSearchProviderOrigin() and adds an explicit if (!dse_origin.IsSameOriginWith(url)) return false; guard, placed before the existing IsPrewarmUrl check, so the synthetic registration is only used on the search provider’s own origin. This tightens the check by requiring exact same-origin equality with the primary search URL rather than any alternate-URL match.

Key insight
The gate conflated “matches the default search engine” (which includes cross-origin alternate_urls) with “is the default search engine’s own origin”; the fix restores the intended invariant by requiring the navigated url to be same-origin with the DSE’s primary search URL before allowing a synthetic registration.

Attack Path

  1. Configure an alternate-origin search entry A default search provider (or a mechanism that sets one) lists an alternate_url on a different origin, e.g. https://other.test/{searchTerms}, while its primary search URL is on example.com.
  2. Navigate to the foreign origin The victim navigates to a URL on other.test, which the pre-patch gate accepts because default-search matching honored the alternate URL.
  3. Synthetic registration is returned IsServiceWorkerSyntheticResponseAllowed returns true, so a synthetic Service Worker registration is created for other.test and returned ahead of the storage lookup for other.test’s own registration.
  4. Cross-origin authorization violation The foreign origin is served the search provider’s synthetic worker context instead of (or before) its legitimate registration, causing incorrect authorization in ServiceWorker (inferred as to concrete downstream leakage, which the diff does not demonstrate).

Impact Assessment

An attacker who can influence the default search provider’s alternate_urls can cause navigations to an unrelated origin to be served a synthetic Service Worker registration intended only for the search provider, i.e. incorrect authorization in ServiceWorker within the browser process’s navigation path. The precondition is a DSE configuration whose alternate URLs span a different origin than the primary search URL. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3. Any further escalation is (inferred).

Files Changed

  • chrome/browser/chrome_content_browser_client.cc
  • chrome/browser/chrome_content_browser_client_unittest.cc

Audit Directions

  • Alternate-URL / provider matching
    Audit every use of default-search-provider matching (IsDefaultSearchEngine and related helpers) that gates a security decision, since these match alternate_urls on arbitrary origins rather than the primary search origin.
  • Origin-vs-match confusion
    Look for gates that treat “matches a configured pattern set” as equivalent to “is the trusted origin,” and verify they perform an explicit IsSameOriginWith against a single canonical origin.
  • Registration returned before storage lookup
    Review synthetic/precomputed registration shortcuts that are returned ahead of the real storage lookup, ensuring their eligibility is bounded to the exact origin they claim to represent.
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