Medium watch CVSS 8.8 chrome Authorization Error 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper privilege management in Regional Capabilities
DescriptionImproper privilege management in Regional Capabilities
ComponentRegional Capabilities
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.
Tracker513607252
Fix commit2e421e49757b (chromium/src) +254/-35
CWECWE-269
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-15 (per vendor advisory)
Fix landed2026-07-03 (public git)
Disclosed2026-08-25
Patch gap53 days — fix public in source before the advisory

Background

Default search engine (DSE) propagation
when Chrome forks/creates a new profile (e.g., during Dice sign-in interception), it copies the source profile’s DSE choice data into the new profile so both share the same default search.
`ChoiceData`
a struct capturing the DSE choice (timestamp, Chrome version, and TemplateURLData for the default engine) to be replayed into the new profile.
`default_search_provider_source()`
reports where the active default search provider came from: FROM_USER, FROM_FALLBACK, FROM_EXTENSION, FROM_POLICY, or FROM_POLICY_RECOMMENDED.
Extension/policy-controlled DSE
a default search engine imposed by an installed extension or an enterprise policy rather than chosen by the user.

Root Cause Analysis

SearchEngineChoiceDialogService::GetChoiceDataFromProfile captured the active default search engine from the source profile unconditionally, returning a ChoiceData regardless of whether the default was set by the user, an extension, a policy, or the built-in fallback. SearchEngineChoiceDialogService::UpdateProfileFromChoiceData then wrote that captured engine into the newly created profile via SetUserSelectedDefaultSearchProvider, effectively converting an extension- or policy-scoped default into a user-selected default in a different profile. This broke the privilege/scope boundary: an extension’s or a specific profile’s policy-imposed search engine choice should not silently follow the user into a freshly forked profile where that extension/policy may not apply.

The fix makes GetChoiceDataFromProfile switch on default_search_provider_source() and return std::nullopt for FROM_FALLBACK, FROM_EXTENSION, FROM_POLICY, and FROM_POLICY_RECOMMENDED, propagating only genuine FROM_USER choices; UpdateProfileFromChoiceData now takes std::optional<ChoiceData> and early-returns when it is empty, so nothing is written when propagation is skipped.

Key insight
The choice-capture logic ignored the provenance of the active default search engine, letting extension- and policy-controlled (or fallback) defaults propagate into new profiles as if user-selected; the fix gates propagation on default_search_provider_source() == FROM_USER and returns std::nullopt otherwise.

Attack Path

  1. Impose a non-user default An extension installed in the source profile (or an enterprise policy) sets the profile’s default search engine, so default_search_provider_source() is FROM_EXTENSION (or FROM_POLICY).
  2. Trigger profile forking The user signs in such that Dice web-signin interception or TurnSyncOnHelper::CreateNewSignedInProfile creates a new profile and captures the source profile’s choice data.
  3. Capture the imposed default Pre-patch, GetChoiceDataFromProfile returns a ChoiceData holding the extension/policy-imposed engine without recording its source.
  4. Persist as user-selected in the new profile UpdateProfileFromChoiceData calls SetUserSelectedDefaultSearchProvider on the new profile, installing the imposed engine there as a user choice beyond the scope of the original extension/policy.

Impact Assessment

An extension- or policy-controlled default search engine from one profile could be propagated into a newly created profile and recorded there as the user’s own selection, an improper privilege/scope escalation of the search-engine choice across profiles. This affects the browser process’s profile-creation flow and requires a profile-forking event (e.g., signed-in profile creation) with a non-user default in the source profile. The advisory classifies this as improper privilege management in Regional Capabilities and does not report exploitation in the wild; the new Search.ChoiceDebug.PropagatedDataOutcome histogram was added to measure the change.

Changed Functions

FunctionChangeNotes
GetChoiceDataFromProfile
chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
modified
if
chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
modified

Files Changed

  • tools/metrics/histograms/metadata/search/enums.xml
  • chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
  • chrome/browser/signin/dice_web_signin_interceptor.cc
  • chrome/browser/signin/dice_web_signin_interceptor.h
  • chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
  • tools/metrics/histograms/metadata/search/histograms.xml
  • chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
  • chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
  • chrome/browser/ui/webui/signin/turn_sync_on_helper.h

Audit Directions

  • Provenance-blind state copying
    Look for code that copies user-settings state (search engine, homepage, permissions) between profiles or contexts without checking whether the setting was user-chosen versus extension/policy/fallback-imposed.
  • Managed/extension-controlled prefs crossing boundaries
    Audit places that read an “active” value (default search, proxy, etc.) and persist it elsewhere as user-selected, bypassing the original controlling scope.
  • Optional-return refactors as security fixes
    Review callers of Get*FromProfile-style helpers to ensure a skipped/empty capture is handled with an early return rather than a default-constructed value being written downstream.
From 2e421e49757bf4231412a3f46d756df41df71272 Mon Sep 17 00:00:00 2001
From: Nicolas Dossou-Gbete <[email protected]>
Date: Fri, 03 Jul 2026 09:21:44 -0700
Subject: [PATCH] Reland "Avoid propagating extension or policy choice across profiles"

This is a reland of commit 1c6acb95514ada701e107ff963b8689281503d71

Fixes the test-specific compilation failure in ChromeForTesting builds,
un-guarding one of the test helper functions that the new tests start
using in CFT builds.

Original change's description:
> Avoid propagating extension or policy choice across profiles
>
> When capturing default search engine (DSE) choice data to propagate to
> forked profiles, skip the propagation for the cases where the active DSE
> is not set by the user (e.g. managed by extension or policies, or
> fallback)
>
> Adds the dedicated UMA histogram
> `Search.ChoiceDebug.PropagatedDataOutcome` to track the impact of this
> change.
>
> Bug: 513607252
> Change-Id: I86c1fdfb6cbe392e4cd7bb8d48dc179f5b07fcd8
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7986107
> Commit-Queue: Nicolas Dossou-Gbété <[email protected]>
> Reviewed-by: David Roger <[email protected]>
> Reviewed-by: James Lee <[email protected]>
> Auto-Submit: Nicolas Dossou-Gbété <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1656537}

Bug: 513607252
Change-Id: I1b6cb0ec5fef5d6a1960ffb876a15064febad439
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8034248
Reviewed-by: James Lee <[email protected]>
Auto-Submit: Nicolas Dossou-Gbété <[email protected]>
Reviewed-by: David Roger <[email protected]>
Commit-Queue: David Roger <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1656620}
---
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
index ad63ef3f..072359d2 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
@@ -292,49 +292,82 @@
 }
 
 // static
-search_engines::ChoiceData
+std::optional<search_engines::ChoiceData>
 SearchEngineChoiceDialogService::GetChoiceDataFromProfile(Profile& profile) {
   PrefService* pref_service = profile.GetPrefs();
   TemplateURLService* template_url_service =
       TemplateURLServiceFactory::GetForProfile(&profile);
   CHECK(template_url_service);
+
+  switch (template_url_service->default_search_provider_source()) {
+    case DefaultSearchManager::FROM_FALLBACK:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedIsFallback);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_EXTENSION:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedIsExtension);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_POLICY:
+    case DefaultSearchManager::FROM_POLICY_RECOMMENDED:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedDueToPolicies);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_USER:
+      break;  // Current default eligible for propagation.
+  }
+
+  CHECK(template_url_service->GetDefaultSearchProvider());
   const TemplateURLData& default_search_engine =
       template_url_service->GetDefaultSearchProvider()->data();
 
-  return {.timestamp = pref_service->GetInt64(
-              prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
-          .chrome_version = pref_service->GetString(
-              prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
-          .default_search_engine = default_search_engine};
+  base::UmaHistogramEnumeration(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      CurrentDefaultPropagationOutcome::kPropagatedCurrentDefault);
+
+  return search_engines::ChoiceData{
+      .timestamp = pref_service->GetInt64(
+          prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
+      .chrome_version = pref_service->GetString(
+          prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
+      .default_search_engine = default_search_engine};
 }
 
 // static
 void SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
     Profile& profile,
-    const search_engines::ChoiceData& choice_data) {
-  PrefService* pref_service = profile.GetPrefs();
-  if (choice_data.timestamp != 0) {
-    pref_service->SetInt64(
-        prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
-        choice_data.timestamp);
-  }
-
-  if (!choice_data.chrome_version.empty()) {
-    pref_service->SetString(
-        prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
-        choice_data.chrome_version);
+    const std::optional<search_engines::ChoiceData>& choice_data) {
+  if (!choice_data.has_value()) {
+    return;
   }
 
   const TemplateURLData& default_search_engine =
-      choice_data.default_search_engine;
-  if (!default_search_engine.keyword().empty() &&
-      !default_search_engine.url().empty()) {
-    TemplateURLService* template_url_service =
-        TemplateURLServiceFactory::GetForProfile(&profile);
-    CHECK(template_url_service);
-    TemplateURL template_url(default_search_engine);
-    template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
+      choice_data->default_search_engine;
+
+  PrefService* pref_service = profile.GetPrefs();
+  if (choice_data->timestamp != 0) {
+    pref_service->SetInt64(
+        prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
+        choice_data->timestamp);
   }
+
+  if (!choice_data->chrome_version.empty()) {
+    pref_service->SetString(
+        prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
+        choice_data->chrome_version);
+  }
+
+  TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(&profile);
+  CHECK(template_url_service);
+  TemplateURL template_url(default_search_engine);
+  template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
 }
 
 TemplateURL::TemplateURLVector
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
index 6b83b61..9b9a9482 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
 #define CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
 
+#include <optional>
 #include <string>
 
 #include "base/containers/flat_map.h"
@@ -152,12 +153,28 @@
   static void SetDialogDisabledForTests(bool dialog_disabled);
 
   // Returns a copy of the `ChoiceData` specific to `profile`.
-  static search_engines::ChoiceData GetChoiceDataFromProfile(Profile& profile);
+  // LINT.IfChange(CurrentDefaultPropagationOutcome)
+  enum class CurrentDefaultPropagationOutcome {
+    kPropagatedCurrentDefault = 0,
+    kSkippedDueToPolicies = 1,
+    kSkippedIsFallback = 2,
+    kSkippedIsExtension = 3,
+    kMaxValue = kSkippedIsExtension,
+  };
+  // LINT.ThenChange(/tools/metrics/histograms/metadata/search/enums.xml:CurrentDefaultPropagationOutcome)
+
+  // Returns a copy of the `ChoiceData` specific to `profile`, or `std::nullopt`
+  // if there is no default search engine to propagate. This function ignores
+  // any extension-provided default search engine to capture the underlying user
+  // choice (or system default), and skips propagation entirely if the default
+  // search is managed by an enterprise policy.
+  static std::optional<search_engines::ChoiceData> GetChoiceDataFromProfile(
+      Profile& profile);
 
   // Updates `profile` with the values from `choice_data`.
   static void UpdateProfileFromChoiceData(
       Profile& profile,
-      const search_engines::ChoiceData& choice_data);
+      const std::optional<search_engines::ChoiceData>& choice_data);
 
  private:
   friend class SearchEngineChoiceDialogServiceFactory;
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index 00c14b72..c896aa38 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -413,6 +413,11 @@
 DiceWebSigninInterceptor::ProfilePresets::ProfilePresets(SkColor profile_color)
     : profile_color(profile_color) {}
 
+DiceWebSigninInterceptor::ProfilePresets::ProfilePresets(ProfilePresets&&) =
+    default;
+DiceWebSigninInterceptor::ProfilePresets&
+DiceWebSigninInterceptor::ProfilePresets::operator=(ProfilePresets&&) = default;
+
 DiceWebSigninInterceptor::ProfilePresets::~ProfilePresets() = default;
 
 // static
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index 22cc634..d81c43738 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -237,14 +237,14 @@
 
     ~ProfilePresets();
 
-    ProfilePresets(ProfilePresets&&) = default;
-    ProfilePresets& operator=(ProfilePresets&&) = default;
+    ProfilePresets(ProfilePresets&&);
+    ProfilePresets& operator=(ProfilePresets&&);
 
     ProfilePresets(const ProfilePresets&) = delete;
     ProfilePresets& operator=(ProfilePresets&) = delete;
 
     SkColor profile_color = SK_ColorTRANSPARENT;
-    search_engines::ChoiceData search_engine_choice_data;
+    std::optional<search_engines::ChoiceData> search_engine_choice_data;
   };
 
   // Cancels any current signin interception and resets the interceptor to its
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
index f86ec517..5f96c22 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
@@ -389,7 +389,7 @@
 
 void TurnSyncOnHelper::CreateNewSignedInProfile() {
   // Use the same the default search engine in the new profile.
-  search_engines::ChoiceData search_engine_choice_data =
+  std::optional<search_engines::ChoiceData> search_engine_choice_data =
       SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile_);
 
   base::OnceCallback<void(Profile*)> profile_created_callback = base::BindOnce(
@@ -412,7 +412,7 @@
 }
 
 void TurnSyncOnHelper::OnNewSignedInProfileCreated(
-    search_engines::ChoiceData search_engine_choice_data,
+    std::optional<search_engines::ChoiceData> search_engine_choice_data,
     Profile* new_profile) {
   DCHECK(dice_signed_in_profile_creator_);
   dice_signed_in_profile_creator_.reset();
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
index 7b6a4b0..cc0a5d4 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_SIGNIN_TURN_SYNC_ON_HELPER_H_
 
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "base/callback_list.h"
@@ -226,7 +227,7 @@
 
   // Called when the new profile is created.
   void OnNewSignedInProfileCreated(
-      search_engines::ChoiceData search_engine_choice_data,
+      std::optional<search_engines::ChoiceData> search_engine_choice_data,
       Profile* new_profile);
 
   // Returns the SyncService, or nullptr if sync is not allowed.
diff --git a/tools/metrics/histograms/metadata/search/enums.xml b/tools/metrics/histograms/metadata/search/enums.xml
index 85abe60f..ff4c655e 100644
--- a/tools/metrics/histograms/metadata/search/enums.xml
+++ b/tools/metrics/histograms/metadata/search/enums.xml
@@ -214,6 +214,17 @@
   <int value="3" label="Not seen, from Long-press"/>
 </enum>
 
+<!-- LINT.IfChange(CurrentDefaultPropagationOutcome) -->
+
+<enum name="CurrentDefaultPropagationOutcome">
+  <int value="0" label="PropagatedCurrentDefault"/>
+  <int value="1" label="SkippedDueToPolicies"/>
+  <int value="2" label="SkippedIsFallback"/>
+  <int value="3" label="SkippedIsExtension"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h:CurrentDefaultPropagationOutcome) -->
+
 <enum name="DonateResult">
   <int value="0" label="No favicons available"/>
   <int value="1" label="Failed"/>
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index cca1f13..749fba3b 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -386,6 +386,17 @@
   </summary>
 </histogram>
 
+<histogram name="Search.ChoiceDebug.PropagatedDataOutcome"
+    enum="CurrentDefaultPropagationOutcome" expires_after="2026-12-01">
+  <owner>[email protected]</owner>
+  <owner>[email protected]</owner>
+  <summary>
+    Records the outcome of attempting to propagate the search engine choice data
+    from an existing profile to a newly created one. Recorded each time a
+    profile forks or creates a new profile while capturing preferences.
+  </summary>
+</histogram>
+
 <histogram name="Search.ChoiceDebug.ScrollStateWithMoreButtonClick"
     enum="SearchEngineChoiceScrollState" expires_after="2026-08-19">
   <owner>[email protected]</owner>
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/country_codes/country_codes.h"
@@ -31,9 +32,11 @@
 #include "components/search_engines/search_engines_pref_names.h"
 #include "components/search_engines/search_engines_switches.h"
 #include "components/search_engines/search_engines_test_util.h"
+#include "components/search_engines/template_url_data_util.h"
 #include "components/search_engines/template_url_prepopulate_data.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/signin_switches.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -63,7 +66,6 @@
 
 constexpr CountryId kBelgiumCountryId("BE");
 
-#if !BUILDFLAG(CHROME_FOR_TESTING)
 void SetUserSelectedDefaultSearchProvider(
     TemplateURLService* template_url_service,
     bool created_by_policy) {
@@ -89,6 +91,7 @@
   template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
 }
 
+#if !BUILDFLAG(CHROME_FOR_TESTING)
 struct TestParam {
   std::string test_suffix;
   std::optional<regional_capabilities::SearchEngineCountryListOverride>
@@ -549,3 +552,141 @@
   ASSERT_EQ(search_engine_choice_dialog_service, nullptr);
 }
 #endif  // !BUILDFLAG(CHROME_FOR_TESTING)
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfilePropagatesCurrentDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // Set a user-selected default search provider to ensure the source is
+  // FROM_USER.
+  SetUserSelectedDefaultSearchProvider(source_template_url_service,
+                                       /*created_by_policy=*/false);
+
+  const TemplateURL* underlying_default =
+      source_template_url_service->GetDefaultSearchProvider();
+  ASSERT_TRUE(underlying_default);
+  const std::string underlying_default_url = underlying_default->url();
+
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  ASSERT_TRUE(choice_data.has_value());
+  EXPECT_EQ(choice_data->default_search_engine.url(), underlying_default_url);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kPropagatedCurrentDefault,
+      1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileIgnoresExtensionProvidedDefault) {
+  base::HistogramTester histogram_tester;
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/country_codes/country_codes.h"
@@ -31,9 +32,11 @@
 #include "components/search_engines/search_engines_pref_names.h"
 #include "components/search_engines/search_engines_switches.h"
 #include "components/search_engines/search_engines_test_util.h"
+#include "components/search_engines/template_url_data_util.h"
 #include "components/search_engines/template_url_prepopulate_data.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/signin_switches.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -63,7 +66,6 @@
 
 constexpr CountryId kBelgiumCountryId("BE");
 
-#if !BUILDFLAG(CHROME_FOR_TESTING)
 void SetUserSelectedDefaultSearchProvider(
     TemplateURLService* template_url_service,
     bool created_by_policy) {
@@ -89,6 +91,7 @@
   template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
 }
 
+#if !BUILDFLAG(CHROME_FOR_TESTING)
 struct TestParam {
   std::string test_suffix;
   std::optional<regional_capabilities::SearchEngineCountryListOverride>
@@ -549,3 +552,141 @@
   ASSERT_EQ(search_engine_choice_dialog_service, nullptr);
 }
 #endif  // !BUILDFLAG(CHROME_FOR_TESTING)
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfilePropagatesCurrentDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // Set a user-selected default search provider to ensure the source is
+  // FROM_USER.
+  SetUserSelectedDefaultSearchProvider(source_template_url_service,
+                                       /*created_by_policy=*/false);
+
+  const TemplateURL* underlying_default =
+      source_template_url_service->GetDefaultSearchProvider();
+  ASSERT_TRUE(underlying_default);
+  const std::string underlying_default_url = underlying_default->url();
+
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  ASSERT_TRUE(choice_data.has_value());
+  EXPECT_EQ(choice_data->default_search_engine.url(), underlying_default_url);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kPropagatedCurrentDefault,
+      1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileIgnoresExtensionProvidedDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // Install an extension-provided default search engine in the source profile.
+  std::unique_ptr<TemplateURLData> extension_data =
+      GenerateDummyTemplateURLData("extension");
+  source_template_url_service->Add(std::make_unique<TemplateURL>(
+      *extension_data, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
+      "extension_id", base::Time(), /*wants_to_be_default_engine=*/true));
+  SetExtensionDefaultSearchInPrefs(profile()->GetTestingPrefService(),
+                                   *extension_data);
+  ASSERT_TRUE(
+      source_template_url_service->IsExtensionControlledDefaultSearch());
+
+  // The choice data captured for propagation to a new profile should be absent
+  // for extensions under the new logic.
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedIsExtension,
+      1);
+
+  // Propagating absent choice data should leave the destination profile's
+  // default search engine unchanged from its own default.
+  TestingProfile* destination_profile =
+      profile_manager()->CreateTestingProfile("Profile 2");
+  TemplateURLServiceFactory::GetInstance()->SetTestingFactory(
+      destination_profile,
+      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+  TemplateURLService* destination_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(destination_profile);
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      destination_template_url_service);
+
+  const std::string destination_default_url =
+      destination_template_url_service->GetDefaultSearchProvider()->url();
+
+  SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
+      *destination_profile, choice_data);
+  EXPECT_FALSE(
+      destination_template_url_service->IsExtensionControlledDefaultSearch());
+  EXPECT_EQ(destination_template_url_service->GetDefaultSearchProvider()->url(),
+            destination_default_url);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileSkipsIfNoUnderlyingDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // When no user choice is made, a fresh profile uses the fallback search
+  // engine, which skips under the new FROM_FALLBACK policy.
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedIsFallback,
+      1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileSkipsIfManagedByPolicy) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  TemplateURLData data;
+  data.SetShortName(u"policy");
+  data.SetKeyword(u"policy");
+  data.SetURL("https://policy/url?bar={searchTerms}");
+  profile()->GetTestingPrefService()->SetManagedPref(
+      DefaultSearchManager::kDefaultSearchProviderDataPrefName,
+      TemplateURLDataToDictionary(data));
+
+  ASSERT_TRUE(source_template_url_service->is_default_search_managed());
+
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedDueToPolicies,
+      1);
+
+  // Clear for subsequent tests.
+  profile()->GetTestingPrefService()->RemoveManagedPref(
+      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
+}
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.