Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in SignIn
DescriptionMemory corruption in SignIn
ComponentSignIn
Sub-componentSignin
Bug ClassMemory Corruption
Tracker523264945
Fix commiteccf0c73bc7c (chromium/src) +151/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc
modified
if
chrome/browser/extensions/api/identity/web_auth_flow.cc
modified
if
chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
modified

Files Changed

  • chrome/browser/extensions/api/identity/web_auth_flow.cc
  • components/signin/public/base/signin_switches.cc
  • chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
  • chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
  • chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc
  • chrome/browser/extensions/api/identity/identity_apitest.cc
  • chrome/browser/extensions/api/identity/web_auth_flow.h
  • components/signin/public/base/signin_switches.h
  • chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.cc
From eccf0c73bc7ca248e1b1d22e58773b7c328710a9 Mon Sep 17 00:00:00 2001
From: Ernest Nguyen Hung <ernn@google.com>
Date: Thu, 17 Sep 2026 03:43:56 -0700
Subject: [PATCH] [signin] Attribute `WebAuthFlow` navigations with initiator origin

`WebAuthFlow` loaded the provider URL as a browser-initiated navigation,
even though the URL comes from the extension that called
`chrome.identity.launchWebAuthFlow`.

Pass the calling extension's origin to `WebAuthFlow` and use it as the
initiator of the navigation, so that the load is attributed to the
extension rather than to the browser. This follows the same pattern as
other extension-initiated navigations, e.g. `chrome.tabs.update`.

The Gaia remote consent flow keeps navigating as browser-initiated: its
URL is obtained by Chrome from Gaia and is not supplied by an extension.

Fixed: 523264945
Change-Id: Icb6cf3751a5ca59cb526890bab8758eec9ddba71
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8412565
Commit-Queue: Ernest Nguyen Hung <ernn@google.com>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1699672}
---
diff --git a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc
index 05a8da7..b55579b 100644
--- a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc
+++ b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc
@@ -48,9 +48,11 @@
 
 void GaiaRemoteConsentFlow::Start() {
   if (!web_flow_) {
-    web_flow_ =
-        std::make_unique<WebAuthFlow>(this, profile_, resolution_data_.url,
-                                      WebAuthFlow::INTERACTIVE, user_gesture_);
+    // No initiator origin: `resolution_data_.url` is not controlled by the
+    // extension, it is obtained by Chrome from Gaia.
+    web_flow_ = std::make_unique<WebAuthFlow>(
+        this, profile_, resolution_data_.url, WebAuthFlow::INTERACTIVE,
+        user_gesture_, /*initiator_origin=*/std::nullopt);
   }
 
   if (resolution_data_.cookies.empty()) {
diff --git a/chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.cc b/chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.cc
index 7fa83ae..000f2eb7 100644
--- a/chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.cc
@@ -6,9 +6,11 @@
 
 #include <algorithm>
 #include <memory>
+#include <optional>
 #include <utility>
 
 #include "base/check_is_test.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -18,9 +20,11 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/identity.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/base/signin_switches.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/buildflags/buildflags.h"
 #include "net/cookies/cookie_util.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/extensions/api/identity/launch_web_auth_flow_delegate_ash.h"
@@ -198,10 +202,16 @@
     return;
   }
 
+  std::optional<url::Origin> initiator_origin =
+      base::FeatureList::IsEnabled(
+          switches::kExtensionWebAuthFlowInitiatorOrigin)
+          ? std::make_optional(extension()->origin())
+          : std::nullopt;
+
   auth_flow_ = std::make_unique<WebAuthFlow>(
       this, profile, auth_url, mode, user_gesture(),
-      abort_on_load_for_non_interactive, timeout_for_non_interactive,
-      popup_bounds);
+      std::move(initiator_origin), abort_on_load_for_non_interactive,
+      timeout_for_non_interactive, popup_bounds);
 
   // An extension might call `launchWebAuthFlow()` with any URL. Add an infobar
   // to attribute displayed URL to the extension.
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chrome/browser/extensions/api/identity/web_auth_flow.cc
index 3c1c43e4..7f5c460 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.cc
@@ -33,6 +33,7 @@
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/browser/ui_util.h"
@@ -44,6 +45,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 #include "url/url_constants.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -73,6 +75,7 @@
     const GURL& provider_url,
     Mode mode,
     bool user_gesture,
+    std::optional<url::Origin> initiator_origin,
     AbortOnLoad abort_on_load_for_non_interactive,
     std::optional<base::TimeDelta> timeout_for_non_interactive,
     std::optional<gfx::Rect> popup_bounds)
@@ -81,6 +84,7 @@
       provider_url_(provider_url),
       mode_(mode),
       user_gesture_(user_gesture),
+      initiator_origin_(std::move(initiator_origin)),
       abort_on_load_for_non_interactive_(abort_on_load_for_non_interactive),
       timeout_for_non_interactive_(timeout_for_non_interactive),
       non_interactive_timeout_timer_(std::make_unique<base::OneShotTimer>()),
@@ -141,6 +145,20 @@
   WebContentsObserver::Observe(web_contents_.get());
 
   content::NavigationController::LoadURLParams load_params(provider_url_);
+
+  if (initiator_origin_) {
+    // `provider_url_` was supplied by an extension, so the navigation must be
+    // attributed to it. Otherwise the network stack treats it as a trusted
+    // top-level navigation initiated by the user, see
+    // https://crbug.com/523264945.
+    load_params.is_renderer_initiated = true;
+    load_params.initiator_origin = initiator_origin_;
+    // `source_site_instance` needs to be set so that a renderer process
+    // compatible with `initiator_origin` is picked by Site Isolation.
+    load_params.source_site_instance = content::SiteInstance::CreateForURL(
+        profile_, initiator_origin_->GetURL());
+  }
+
   web_contents_->GetController().LoadURLWithParams(load_params);
 
   MaybeStartTimeout();
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.h b/chrome/browser/extensions/api/identity/web_auth_flow.h
index d22884d..2b52340 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.h
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.h
@@ -21,6 +21,7 @@
 #include "extensions/buildflags/buildflags.h"
 #include "ui/gfx/geometry/rect.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
 
@@ -102,12 +103,19 @@
 
   // Creates an instance with the given parameters.
   // Caller owns `delegate`.
+  //
+  // `initiator_origin` must be the origin of the extension that supplied
+  // `provider_url`, so that the resulting navigation is attributed to it
+  // instead of being treated as a trusted, browser-initiated navigation. Pass
+  // `std::nullopt` only when `provider_url` does not originate from web or
+  // extension content.
   WebAuthFlow(
       Delegate* delegate,
       Profile* profile,
       const GURL& provider_url,
       Mode mode,
       bool user_gesture,
+      std::optional<url::Origin> initiator_origin,
       AbortOnLoad abort_on_load_for_non_interactive = AbortOnLoad::kYes,
       std::optional<base::TimeDelta> timeout_for_non_interactive = std::nullopt,
       std::optional<gfx::Rect> popup_bounds = std::nullopt);
@@ -174,6 +182,9 @@
   const GURL provider_url_;
   const Mode mode_;
   const bool user_gesture_;
+  // Origin to attribute the navigation to, or `std::nullopt` for a
+  // browser-initiated navigation. See the constructor for details.
+  const std::optional<url::Origin> initiator_origin_;
 
   // WebContents used to initialize the authentication. It is not displayed
   // and not owned by browser window. This WebContents is observed by
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc
index 7f2d0f7..cb446f0 100644
--- a/components/signin/public/base/signin_switches.cc
+++ b/components/signin/public/base/signin_switches.cc
@@ -666,6 +666,11 @@
         base::Hours(8)};
 #endif
 
+#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
+BASE_FEATURE(kExtensionWebAuthFlowInitiatorOrigin,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif
+
 BASE_FEATURE(kFetchAccountInfoOnRestart, base::FEATURE_DISABLED_BY_DEFAULT);
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h
index a5857ad..adf2a3d 100644
--- a/components/signin/public/base/signin_switches.h
+++ b/components/signin/public/base/signin_switches.h
@@ -533,6 +533,14 @@
     kPolicyDisclaimerRegistrationRetryDelay;
 #endif
 
+#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
+// Controls whether the navigation performed by
+// `chrome.identity.launchWebAuthFlow` is attributed with the extesion's origin.
+// Introduced as a kill switch.
+COMPONENT_EXPORT(SIGNIN_SWITCHES)
+BASE_DECLARE_FEATURE(kExtensionWebAuthFlowInitiatorOrigin);
+#endif
+
 // Feature flag to fetch AccountInfo (UserInfo & Capabilities) on restart.
 COMPONENT_EXPORT(SIGNIN_SWITCHES)
 BASE_DECLARE_FEATURE(kFetchAccountInfoOnRestart);
diff --git a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
index 77d7d35..f48dca7 100644
--- a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
+++ b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
@@ -29,7 +29,8 @@
                     nullptr,
                     GURL(),
                     WebAuthFlow::INTERACTIVE,
-                    /*user_gesture=*/true) {}
+                    /*user_gesture=*/true,
+                    /*initiator_origin=*/std::nullopt) {}
 
   ~FakeWebAuthFlow() override = default;
 
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index fd8bc36c..f3769f6b 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -147,6 +147,7 @@
 using ::extensions::ExtensionsAPIClient;
 using ::testing::_;
 using ::testing::HasSubstr;
+using ::testing::Not;
 using ::testing::Return;
 using ::testing::StartsWith;
 
@@ -3836,6 +3837,57 @@
       IdentityLaunchWebAuthFlowFunction::Error::kInteractionRequired, 1);
 }
 
+// Navigations started by `launchWebAuthFlow` must be attributed to the
+// calling extension, so that the target site sees a cross-site request and
+// `SameSite=Strict` cookies are withheld. See https://crbug.com/523264945.
+IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest,
+                       NavigationIsAttributedToExtension) {
+  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+  https_server.AddDefaultHandlers(base::FilePath(
+      FILE_PATH_LITERAL("chrome/test/data/extensions/api_test/identity")));
+
+  net::test_server::HttpRequest::HeaderMap headers;
+  https_server.RegisterRequestMonitor(base::BindLambdaForTesting(
+      [&](const net::test_server::HttpRequest& request) {
+        if (request.GetURL().path() == "/interaction_required.html") {
+          headers = request.headers;
+        }
+      }));
+  ASSERT_TRUE(https_server.Start());
+
+  const GURL auth_url(https_server.GetURL("/interaction_required.html"));
+  ASSERT_TRUE(content::SetCookie(profile(), auth_url,
+                                 "strict_cookie=1; SameSite=Strict; Secure"));
+  ASSERT_TRUE(content::SetCookie(profile(), auth_url,
+                                 "lax_cookie=1; SameSite=Lax; Secure"));
+
+  scoped_refptr<IdentityLaunchWebAuthFlowFunction> function =
+      CreateLaunchWebAuthFlowFunction();
+  scoped_refptr<const Extension> extension = function->extension();
+
+  content::TestNavigationObserver nav_observer(auth_url);
+  nav_observer.StartWatchingNewWebContents();
+
+  const std::string args = base::StringPrintf(
+      R"([{"interactive": false, "url": "%s"}])", auth_url.spec().c_str());
+  ASSERT_EQ(utils::RunFunctionAndReturnError(function.get(), args, profile()),
+            errors::kInteractionRequired);
+
+  nav_observer.Wait();
+  ASSERT_TRUE(nav_observer.last_navigation_succeeded());
+  EXPECT_EQ(nav_observer.last_initiator_origin(), extension->origin());
+
+  // The request must not look like a trusted, user-initiated navigation.
+  ASSERT_TRUE(headers.contains("Sec-Fetch-Site"));
+  EXPECT_EQ(headers["Sec-Fetch-Site"], "cross-site");
+
+  // `SameSite=Strict` cookies must be withheld. `SameSite=Lax` cookies are
+  // still sent.
+  ASSERT_TRUE(headers.contains("Cookie"));
+  EXPECT_THAT(headers["Cookie"], Not(HasSubstr("strict_cookie=1")));
+  EXPECT_THAT(headers["Cookie"], HasSubstr("lax_cookie=1"));
+}
+
 // Checks that, by default, when a page fully loads in silent mode and doesn't
 // redirect, `launchWebAuthFlow()` terminates with an error.
 IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest,
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
index b3a4c94..0b27a88f 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
@@ -39,6 +39,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/base_window.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/sessions/session_restore.h"
@@ -124,15 +125,17 @@
       WebAuthFlow::AbortOnLoad abort_on_load_for_non_interactive =
           WebAuthFlow::AbortOnLoad::kYes,
       std::optional<base::TimeDelta> timeout_for_non_interactive = std::nullopt,
-      std::optional<gfx::Rect> popup_bounds = std::nullopt) {
+      std::optional<gfx::Rect> popup_bounds = std::nullopt,
+      std::optional<url::Origin> initiator_origin = std::nullopt) {
     if (!profile) {
       profile = GetProfile();
     }
 
     web_auth_flow_ = std::make_unique<WebAuthFlow>(
         &mock_web_auth_flow_delegate_, profile, url, mode,
-        /*user_gesture=*/true, abort_on_load_for_non_interactive,
-        timeout_for_non_interactive, popup_bounds);
+        /*user_gesture=*/true, std::move(initiator_origin),
+        abort_on_load_for_non_interactive, timeout_for_non_interactive,
+        popup_bounds);
 
     timeout_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
     web_auth_flow_->SetClockForTesting(timeout_task_runner_->GetMockTickClock(),
@@ -899,6 +902,38 @@
       StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE));
 }
 
+// The navigation must be attributed to the origin that supplied the URL, so
+// that it is not treated as a trusted browser-initiated navigation. See
+// https://crbug.com/523264945.
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, InitiatorOriginPropagated) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+  const url::Origin initiator_origin = url::Origin::Create(
+      GURL("chrome-extension://abcdefghijklmnopabcdefghijklmnop/"));
+
+  WebAuthFlowTestNavigationObserver navigation_observer(auth_url);
+  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE,
+                   /*profile=*/nullptr, WebAuthFlow::AbortOnLoad::kYes,
+                   /*timeout_for_non_interactive=*/std::nullopt,
+                   /*popup_bounds=*/std::nullopt, initiator_origin);
+  navigation_observer.WaitForWindow(web_auth_flow());
+
+  EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+  EXPECT_EQ(initiator_origin, navigation_observer.last_initiator_origin());
+}
+
+// Flows that don't pass an initiator origin (i.e. the Gaia remote consent
+// flow) keep navigating as browser-initiated.
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, NoInitiatorOriginByDefault) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+
+  WebAuthFlowTestNavigationObserver navigation_observer(auth_url);
+  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
+  navigation_observer.WaitForWindow(web_auth_flow());
+
+  EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+  EXPECT_EQ(std::nullopt, navigation_observer.last_initiator_origin());
+}
+
 #if BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
 IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                        PopupCreationFailure_CallsCannotCreateWindow) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
index 77d7d35..f48dca7 100644
--- a/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
+++ b/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc
@@ -29,7 +29,8 @@
                     nullptr,
                     GURL(),
                     WebAuthFlow::INTERACTIVE,
-                    /*user_gesture=*/true) {}
+                    /*user_gesture=*/true,
+                    /*initiator_origin=*/std::nullopt) {}
 
   ~FakeWebAuthFlow() override = default;
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index fd8bc36c..f3769f6b 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -147,6 +147,7 @@
 using ::extensions::ExtensionsAPIClient;
 using ::testing::_;
 using ::testing::HasSubstr;
+using ::testing::Not;
 using ::testing::Return;
 using ::testing::StartsWith;
 
@@ -3836,6 +3837,57 @@
       IdentityLaunchWebAuthFlowFunction::Error::kInteractionRequired, 1);
 }
 
+// Navigations started by `launchWebAuthFlow` must be attributed to the
+// calling extension, so that the target site sees a cross-site request and
+// `SameSite=Strict` cookies are withheld. See https://crbug.com/523264945.
+IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest,
+                       NavigationIsAttributedToExtension) {
+  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+  https_server.AddDefaultHandlers(base::FilePath(
+      FILE_PATH_LITERAL("chrome/test/data/extensions/api_test/identity")));
+
+  net::test_server::HttpRequest::HeaderMap headers;
+  https_server.RegisterRequestMonitor(base::BindLambdaForTesting(
+      [&](const net::test_server::HttpRequest& request) {
+        if (request.GetURL().path() == "/interaction_required.html") {
+          headers = request.headers;
+        }
+      }));
+  ASSERT_TRUE(https_server.Start());
+
+  const GURL auth_url(https_server.GetURL("/interaction_required.html"));
+  ASSERT_TRUE(content::SetCookie(profile(), auth_url,
+                                 "strict_cookie=1; SameSite=Strict; Secure"));
+  ASSERT_TRUE(content::SetCookie(profile(), auth_url,
+                                 "lax_cookie=1; SameSite=Lax; Secure"));
+
+  scoped_refptr<IdentityLaunchWebAuthFlowFunction> function =
+      CreateLaunchWebAuthFlowFunction();
+  scoped_refptr<const Extension> extension = function->extension();
+
+  content::TestNavigationObserver nav_observer(auth_url);
+  nav_observer.StartWatchingNewWebContents();
+
+  const std::string args = base::StringPrintf(
+      R"([{"interactive": false, "url": "%s"}])", auth_url.spec().c_str());
+  ASSERT_EQ(utils::RunFunctionAndReturnError(function.get(), args, profile()),
+            errors::kInteractionRequired);
+
+  nav_observer.Wait();
+  ASSERT_TRUE(nav_observer.last_navigation_succeeded());
+  EXPECT_EQ(nav_observer.last_initiator_origin(), extension->origin());
+
+  // The request must not look like a trusted, user-initiated navigation.
+  ASSERT_TRUE(headers.contains("Sec-Fetch-Site"));
+  EXPECT_EQ(headers["Sec-Fetch-Site"], "cross-site");
+
+  // `SameSite=Strict` cookies must be withheld. `SameSite=Lax` cookies are
+  // still sent.
+  ASSERT_TRUE(headers.contains("Cookie"));
+  EXPECT_THAT(headers["Cookie"], Not(HasSubstr("strict_cookie=1")));
+  EXPECT_THAT(headers["Cookie"], HasSubstr("lax_cookie=1"));
+}
+
 // Checks that, by default, when a page fully loads in silent mode and doesn't
 // redirect, `launchWebAuthFlow()` terminates with an error.
 IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest,
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
index b3a4c94..0b27a88f 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow_browsertest.cc
@@ -39,6 +39,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/base_window.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/sessions/session_restore.h"
@@ -124,15 +125,17 @@
       WebAuthFlow::AbortOnLoad abort_on_load_for_non_interactive =
           WebAuthFlow::AbortOnLoad::kYes,
       std::optional<base::TimeDelta> timeout_for_non_interactive = std::nullopt,
-      std::optional<gfx::Rect> popup_bounds = std::nullopt) {
+      std::optional<gfx::Rect> popup_bounds = std::nullopt,
+      std::optional<url::Origin> initiator_origin = std::nullopt) {
     if (!profile) {
       profile = GetProfile();
     }
 
     web_auth_flow_ = std::make_unique<WebAuthFlow>(
         &mock_web_auth_flow_delegate_, profile, url, mode,
-        /*user_gesture=*/true, abort_on_load_for_non_interactive,
-        timeout_for_non_interactive, popup_bounds);
+        /*user_gesture=*/true, std::move(initiator_origin),
+        abort_on_load_for_non_interactive, timeout_for_non_interactive,
+        popup_bounds);
 
     timeout_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
     web_auth_flow_->SetClockForTesting(timeout_task_runner_->GetMockTickClock(),
@@ -899,6 +902,38 @@
       StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE));
 }
 
+// The navigation must be attributed to the origin that supplied the URL, so
+// that it is not treated as a trusted browser-initiated navigation. See
+// https://crbug.com/523264945.
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, InitiatorOriginPropagated) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+  const url::Origin initiator_origin = url::Origin::Create(
+      GURL("chrome-extension://abcdefghijklmnopabcdefghijklmnop/"));
+
+  WebAuthFlowTestNavigationObserver navigation_observer(auth_url);
+  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE,
+                   /*profile=*/nullptr, WebAuthFlow::AbortOnLoad::kYes,
+                   /*timeout_for_non_interactive=*/std::nullopt,
+                   /*popup_bounds=*/std::nullopt, initiator_origin);
+  navigation_observer.WaitForWindow(web_auth_flow());
+
+  EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+  EXPECT_EQ(initiator_origin, navigation_observer.last_initiator_origin());
+}
+
+// Flows that don't pass an initiator origin (i.e. the Gaia remote consent
+// flow) keep navigating as browser-initiated.
+IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, NoInitiatorOriginByDefault) {
+  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
+
+  WebAuthFlowTestNavigationObserver navigation_observer(auth_url);
+  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
+  navigation_observer.WaitForWindow(web_auth_flow());
+
+  EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+  EXPECT_EQ(std::nullopt, navigation_observer.last_initiator_origin());
+}
+
 #if BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
 IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                        PopupCreationFailure_CallsCannotCreateWindow) {
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.