CVE-2026-95327
Overview
Background
- `GoogleURLLoaderThrottle`
- a
blink::URLLoaderThrottlethat Chrome attaches to network requests to inject Google-specific headers such as SafeSearch and app-restriction controls. - `safe_search_api::kGoogleAppsAllowedDomains`
- the
X-GoogApps-Allowed-DomainsCORS-exempt request header that carries the enterprise-configured list of Google Workspace domains a managed device is restricted to. - `allowed_domains_for_apps`
- the field on
dynamic_params_holding that policy-configured domain list, which is only meaningful to Google servers. - `HttpRequestHeadersUpdateParams`
- the structure whose
modified_cors_exempt_headersandremoved_headersfields let a throttle add or strip headers when a request is redirected.
Root Cause Analysis
When a request started against a Google domain, GoogleURLLoaderThrottle set the safe_search_api::kGoogleAppsAllowedDomains CORS-exempt header carrying dynamic_params_->allowed_domains_for_apps. On redirect, the original code only re-set this header when redirect_info->new_url still matched DomainIs("google.com"), and did nothing otherwise. Because the header had already been applied to the underlying request, taking no action on a redirect to a non-Google origin left the header in place, so it was carried across the cross-origin hop and sent to the redirect target. The invariant that the enterprise domain list must only ever reach Google endpoints was therefore violated.
The fix adds an else branch that pushes safe_search_api::kGoogleAppsAllowedDomains onto headers_update_params->removed_headers whenever the redirect target is not a Google domain, guaranteeing the header is stripped before the request leaves Google origins.
Attack Path
- Managed configuration
A device under enterprise policy has
allowed_domains_for_appspopulated, so requests to Google domains carry thesafe_search_api::kGoogleAppsAllowedDomainsheader. - Attacker-controlled redirect
The user is induced to load a Google URL that server-side redirects, or an attacker positions a resource whose Google endpoint issues a
3xxredirect to a non-Google origin they control. - Header retained on redirect
During
WillRedirectRequest, the old code takes the no-op path for the non-Googleredirect_info->new_url, leaving the previously attached header on the request. - Cross-origin leak
The redirected request is sent to the attacker’s origin with the
X-GoogApps-Allowed-Domainsvalue intact, disclosing the enterprise-configured domain list.
Impact Assessment
safe_search_api::kGoogleAppsAllowedDomains header, leaking the enterprise’s configured Google Workspace domain allowlist to an untrusted server. The disclosure happens in the network request context of a managed Chrome client and requires that the device have allowed_domains_for_apps configured and that the victim follow a cross-origin redirect chain originating from a Google domain.Changed Functions
| Function | Change | Notes |
|---|---|---|
MockThrottleDelegatechrome/common/google_url_loader_throttle_unittest.cc |
modified | |
GoogleURLLoaderThrottleTestchrome/common/google_url_loader_throttle_unittest.cc |
modified | |
ifchrome/common/google_url_loader_throttle_unittest.cc |
modified | |
GoogleURLLoaderThrottleBoundSessionCredentialsTestchrome/common/google_url_loader_throttle_unittest.cc |
modified | |
forchrome/common/google_url_loader_throttle_unittest.cc |
modified | |
TEST_Fchrome/common/google_url_loader_throttle_unittest.cc |
modified |
Files Changed
chrome/common/google_url_loader_throttle_unittest.ccchrome/common/google_url_loader_throttle.cc
Audit Directions
- Redirect header persistenceReview every throttle or delegate that adds request headers on start but only conditionally re-applies them on redirect, since an untouched header survives cross-origin hops unless placed in
removed_headers. - CORS-exempt and sensitive headersAudit code that sets
modified_cors_exempt_headersor other privileged headers to confirm each has a matching strip path when the destination origin changes. - Domain-scoped dataTrace policy- or account-scoped values like
allowed_domains_for_appsto ensure they are gated by aDomainIs(or equivalent origin) check on both the initial request and every redirect target.
Patch
From 724dcd975fc3b693ace5a67fe134c21d2e405923 Mon Sep 17 00:00:00 2001
From: Sebastien Lalancette <seblalancette@chromium.org>
Date: Fri, 21 Aug 2026 13:13:26 -0700
Subject: [PATCH] Remove X-GoogApps special header on redirect to non-Google domains
Updated GoogleURLLoaderThrottle to explicitly remove the
safe_search_api::kGoogleAppsAllowedDomains header when redirecting to a
non-Google domain if allowed_domains_for_apps is configured. This
prevents the header from leaking across cross-origin redirects. Also
added unit tests in GoogleURLLoaderThrottleTest to verify header
addition and removal across start and redirect requests.
Bug: 502077689
Change-Id: I28f5df2bc312ba3e93f16ed2ab93c5b53d825b1a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8263292
Reviewed-by: Theresa Wellington <twellington@chromium.org>
Reviewed-by: Zonghan Xu <xzonghan@chromium.org>
Commit-Queue: Sebastien Lalancette <seblalancette@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1684162}
---
diff --git a/chrome/common/google_url_loader_throttle.cc b/chrome/common/google_url_loader_throttle.cc
index 2c04ddf..3333574 100644
--- a/chrome/common/google_url_loader_throttle.cc
+++ b/chrome/common/google_url_loader_throttle.cc
@@ -264,11 +264,15 @@
dynamic_params_->youtube_restrict));
}
- if (!dynamic_params_->allowed_domains_for_apps.empty() &&
- redirect_info->new_url.DomainIs("google.com")) {
- headers_update_params->modified_cors_exempt_headers.SetHeader(
- safe_search_api::kGoogleAppsAllowedDomains,
- dynamic_params_->allowed_domains_for_apps);
+ if (!dynamic_params_->allowed_domains_for_apps.empty()) {
+ if (redirect_info->new_url.DomainIs("google.com")) {
+ headers_update_params->modified_cors_exempt_headers.SetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains,
+ dynamic_params_->allowed_domains_for_apps);
+ } else {
+ headers_update_params->removed_headers.push_back(
+ safe_search_api::kGoogleAppsAllowedDomains);
+ }
}
#if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/common/google_url_loader_throttle_unittest.cc b/chrome/common/google_url_loader_throttle_unittest.cc
index 531b8af..b35b9ddd 100644
--- a/chrome/common/google_url_loader_throttle_unittest.cc
+++ b/chrome/common/google_url_loader_throttle_unittest.cc
@@ -6,14 +6,19 @@
#include <memory>
#include <string_view>
+#include <vector>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
+#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/common/renderer_configuration.mojom.h"
+#include "components/safe_search_api/safe_search_util.h"
+#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_switches.h"
+#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/http_request_headers_update_params.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
@@ -24,13 +29,180 @@
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "url/origin.h"
-// This file only contains tests relevant to the bound session credentials
-// feature.
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#include "chrome/common/bound_session_request_throttled_handler.h"
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
namespace {
+class MockThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
+ public:
+ MockThrottleDelegate() = default;
+ MOCK_METHOD(void, CancelWithError, (int, std::string_view), (override));
+ MOCK_METHOD(void, Resume, (), (override));
+};
+
+class GoogleURLLoaderThrottleTest : public ::testing::Test {
+ public:
+ const GURL kTestGoogleURL = GURL("https://google.com");
+ const GURL kGoogleSubdomainURL = GURL("https://accounts.google.com");
+
+ GoogleURLLoaderThrottleTest() = default;
+ ~GoogleURLLoaderThrottleTest() override = default;
+
+ void SetAllowedDomainsForApps(const std::string& allowed_domains) {
+ allowed_domains_for_apps_ = allowed_domains;
+ }
+
+ GoogleURLLoaderThrottle* throttle() {
+ if (!throttle_) {
+ CreateThrottle();
+ }
+ return throttle_.get();
+ }
+
+ MockThrottleDelegate* delegate() { return delegate_.get(); }
+
+ protected:
+ virtual void CreateThrottle() {
+ chrome::mojom::DynamicParamsPtr dynamic_params(
+ chrome::mojom::DynamicParams::New());
+ dynamic_params->allowed_domains_for_apps = allowed_domains_for_apps_;
+
+ delegate_ = std::make_unique<MockThrottleDelegate>();
+
+ throttle_ = std::make_unique<GoogleURLLoaderThrottle>(
+#if BUILDFLAG(IS_ANDROID)
+ "",
+#endif
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ /*bound_session_request_throttled_handler=*/nullptr,
+#endif
+ std::move(dynamic_params));
+ throttle_->set_delegate(delegate_.get());
+ }
+
+ // Must outlive `throttle_`.
+ std::unique_ptr<MockThrottleDelegate> delegate_;
+ std::unique_ptr<GoogleURLLoaderThrottle> throttle_;
+ std::string allowed_domains_for_apps_;
+};
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = kTestGoogleURL;
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_EQ(request.cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestGoogleSubdomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = kGoogleSubdomainURL;
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_EQ(request.cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestNonGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = GURL("https://example.com");
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_FALSE(request.cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = kTestGoogleURL;
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_EQ(headers_update_params.modified_cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToGoogleSubdomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = kGoogleSubdomainURL;
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_EQ(headers_update_params.modified_cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToNonGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = GURL("https://example.com");
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_FALSE(headers_update_params.modified_cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::ElementsAre(safe_search_api::kGoogleAppsAllowedDomains));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsEmptyWillRedirectRequestToNonGoogleDomain) {
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = GURL("https://example.com");
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_FALSE(headers_update_params.modified_cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
using chrome::mojom::BoundSessionThrottlerParams;
using chrome::mojom::BoundSessionThrottlerParamsPtr;
using RequestBoundSessionStatus =
@@ -67,13 +239,6 @@
ResumeOrCancelThrottledRequestCallback callback_;
};
-class MockThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
- public:
- MockThrottleDelegate() = default;
- MOCK_METHOD(void, CancelWithError, (int, std::string_view), (override));
- MOCK_METHOD(void, Resume, (), (override));
-};
-
// std::vector<BoundSessionThrottlerParamsPtr> initializer list constructor
// doesn't work for some reason, so tests use these helpers to reduce
// boilerplate.
@@ -92,15 +257,12 @@
return result;
}
-class GoogleURLLoaderThrottleTest
- : public ::testing::Test,
+class GoogleURLLoaderThrottleBoundSessionCredentialsTest
+ : public GoogleURLLoaderThrottleTest,
public ::testing::WithParamInterface<RequestAction> {
public:
- const GURL kTestGoogleURL = GURL("https://google.com");
- const GURL kGoogleSubdomainURL = GURL("https://accounts.google.com");
-
- GoogleURLLoaderThrottleTest() = default;
- ~GoogleURLLoaderThrottleTest() override = default;
+ GoogleURLLoaderThrottleBoundSessionCredentialsTest() = default;
+ ~GoogleURLLoaderThrottleBoundSessionCredentialsTest() override = default;
void ConfigureBoundSessionThrottlerParams(const std::string& domain,
const std::string& path,
@@ -115,15 +277,6 @@
return bound_session_handler_.get();
}
- GoogleURLLoaderThrottle* throttle() {
- if (!throttle_) {
- CreateThrottle();
- }
- return throttle_.get();
- }
-
- MockThrottleDelegate* delegate() { return delegate_.get(); }
-
void CallThrottleAndVerifyDeferExpectation(bool expect_defer,
const GURL& url) {
// `WillStartRequest()` has to be occur before `WillRedirectRequest()`, so
@@ -181,10 +334,11 @@
histogram_tester_ = std::make_unique<base::HistogramTester>();
}
- private:
- void CreateThrottle() {
+ protected:
+ void CreateThrottle() override {
chrome::mojom::DynamicParamsPtr dynamic_params(
chrome::mojom::DynamicParams::New());
+ dynamic_params->allowed_domains_for_apps = allowed_domains_for_apps_;
for (const auto& params : bound_session_throttler_params_) {
dynamic_params->bound_session_throttler_params.push_back(params->Clone());
}
@@ -203,33 +357,34 @@
throttle_->set_delegate(delegate_.get());
}
+ private:
base::test::ScopedFeatureList feature_list_{
switches::kEnableBoundSessionCredentials};
base::test::TaskEnvironment task_environment_;
raw_ptr<FakeBoundSessionRequestThrottledHandler, DanglingUntriaged>
bound_session_handler_ = nullptr;
- // Must outlive `throttle_`.
- std::unique_ptr<MockThrottleDelegate> delegate_;
- std::unique_ptr<GoogleURLLoaderThrottle> throttle_;
std::vector<BoundSessionThrottlerParamsPtr> bound_session_throttler_params_;
std::unique_ptr<base::HistogramTester> histogram_tester_ =
std::make_unique<base::HistogramTester>();
};
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusNullParams) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusNullParams) {
EXPECT_EQ(
GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(kTestGoogleURL, {}),
RequestBoundSessionStatus::kNotCovered);
}
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusEmptyParams) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusEmptyParams) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
kTestGoogleURL, ToVector(BoundSessionThrottlerParams::New(
"", "", base::Time::Now()))),
RequestBoundSessionStatus::kNotCovered);
}
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusCookieFresh) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusCookieFresh) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
kTestGoogleURL,
ToVector(BoundSessionThrottlerParams::New(
@@ -237,7 +392,7 @@
RequestBoundSessionStatus::kCoveredWithFreshCookie);
}
-TEST_F(GoogleURLLoaderThrottleTest,
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
GetRequestBoundSessionStatusNotInBoundSession) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
GURL("https://youtube.com"),
@@ -246,14 +401,15 @@
RequestBoundSessionStatus::kNotCovered);
}
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusCookieExpired) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusCookieExpired) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
kTestGoogleURL, ToVector(BoundSessionThrottlerParams::New(
"google.com", "/", base::Time::Min()))),
RequestBoundSessionStatus::kCoveredWithMissingCookie);
}
-TEST_F(GoogleURLLoaderThrottleTest,
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
GetRequestBoundSessionStatusCookieExpiresNow) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
kTestGoogleURL, ToVector(BoundSessionThrottlerParams::New(
@@ -261,7 +417,7 @@
RequestBoundSessionStatus::kCoveredWithMissingCookie);
}
-TEST_F(GoogleURLLoaderThrottleTest,
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
GetRequestBoundSessionStatusCookieExpiredDomainWithLeadingDot) {
EXPECT_EQ(GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(
kTestGoogleURL, ToVector(BoundSessionThrottlerParams::New(
@@ -269,7 +425,8 @@
RequestBoundSessionStatus::kCoveredWithMissingCookie);
}
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusSubdomainUrl) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusSubdomainUrl) {
... (file diff truncated)
Regression Test / PoC
diff --git a/chrome/common/google_url_loader_throttle_unittest.cc b/chrome/common/google_url_loader_throttle_unittest.cc
index 531b8af..b35b9ddd 100644
--- a/chrome/common/google_url_loader_throttle_unittest.cc
+++ b/chrome/common/google_url_loader_throttle_unittest.cc
@@ -6,14 +6,19 @@
#include <memory>
#include <string_view>
+#include <vector>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
+#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/common/renderer_configuration.mojom.h"
+#include "components/safe_search_api/safe_search_util.h"
+#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_switches.h"
+#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/http_request_headers_update_params.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
@@ -24,13 +29,180 @@
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "url/origin.h"
-// This file only contains tests relevant to the bound session credentials
-// feature.
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#include "chrome/common/bound_session_request_throttled_handler.h"
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
namespace {
+class MockThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
+ public:
+ MockThrottleDelegate() = default;
+ MOCK_METHOD(void, CancelWithError, (int, std::string_view), (override));
+ MOCK_METHOD(void, Resume, (), (override));
+};
+
+class GoogleURLLoaderThrottleTest : public ::testing::Test {
+ public:
+ const GURL kTestGoogleURL = GURL("https://google.com");
+ const GURL kGoogleSubdomainURL = GURL("https://accounts.google.com");
+
+ GoogleURLLoaderThrottleTest() = default;
+ ~GoogleURLLoaderThrottleTest() override = default;
+
+ void SetAllowedDomainsForApps(const std::string& allowed_domains) {
+ allowed_domains_for_apps_ = allowed_domains;
+ }
+
+ GoogleURLLoaderThrottle* throttle() {
+ if (!throttle_) {
+ CreateThrottle();
+ }
+ return throttle_.get();
+ }
+
+ MockThrottleDelegate* delegate() { return delegate_.get(); }
+
+ protected:
+ virtual void CreateThrottle() {
+ chrome::mojom::DynamicParamsPtr dynamic_params(
+ chrome::mojom::DynamicParams::New());
+ dynamic_params->allowed_domains_for_apps = allowed_domains_for_apps_;
+
+ delegate_ = std::make_unique<MockThrottleDelegate>();
+
+ throttle_ = std::make_unique<GoogleURLLoaderThrottle>(
+#if BUILDFLAG(IS_ANDROID)
+ "",
+#endif
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ /*bound_session_request_throttled_handler=*/nullptr,
+#endif
+ std::move(dynamic_params));
+ throttle_->set_delegate(delegate_.get());
+ }
+
+ // Must outlive `throttle_`.
+ std::unique_ptr<MockThrottleDelegate> delegate_;
+ std::unique_ptr<GoogleURLLoaderThrottle> throttle_;
+ std::string allowed_domains_for_apps_;
+};
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = kTestGoogleURL;
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_EQ(request.cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestGoogleSubdomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = kGoogleSubdomainURL;
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_EQ(request.cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillStartRequestNonGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ network::ResourceRequest request;
+ request.url = GURL("https://example.com");
+ bool defer = false;
+ throttle()->WillStartRequest(&request, &defer);
+
+ EXPECT_FALSE(request.cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = kTestGoogleURL;
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_EQ(headers_update_params.modified_cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToGoogleSubdomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = kGoogleSubdomainURL;
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_EQ(headers_update_params.modified_cors_exempt_headers.GetHeader(
+ safe_search_api::kGoogleAppsAllowedDomains),
+ "example.com");
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsWillRedirectRequestToNonGoogleDomain) {
+ SetAllowedDomainsForApps("example.com");
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = GURL("https://example.com");
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_FALSE(headers_update_params.modified_cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::ElementsAre(safe_search_api::kGoogleAppsAllowedDomains));
+}
+
+TEST_F(GoogleURLLoaderThrottleTest,
+ AllowedDomainsForAppsEmptyWillRedirectRequestToNonGoogleDomain) {
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = GURL("https://example.com");
+ network::mojom::URLResponseHead response_head;
+ bool defer = false;
+ network::HttpRequestHeadersUpdateParams headers_update_params;
+
+ throttle()->WillRedirectRequest(&redirect_info, response_head, &defer,
+ &headers_update_params);
+
+ EXPECT_FALSE(headers_update_params.modified_cors_exempt_headers.HasHeader(
+ safe_search_api::kGoogleAppsAllowedDomains));
+ EXPECT_THAT(headers_update_params.removed_headers,
+ testing::Not(testing::Contains(
+ safe_search_api::kGoogleAppsAllowedDomains)));
+}
+
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
using chrome::mojom::BoundSessionThrottlerParams;
using chrome::mojom::BoundSessionThrottlerParamsPtr;
using RequestBoundSessionStatus =
@@ -67,13 +239,6 @@
ResumeOrCancelThrottledRequestCallback callback_;
};
-class MockThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
- public:
- MockThrottleDelegate() = default;
- MOCK_METHOD(void, CancelWithError, (int, std::string_view), (override));
- MOCK_METHOD(void, Resume, (), (override));
-};
-
// std::vector<BoundSessionThrottlerParamsPtr> initializer list constructor
// doesn't work for some reason, so tests use these helpers to reduce
// boilerplate.
@@ -92,15 +257,12 @@
return result;
}
-class GoogleURLLoaderThrottleTest
- : public ::testing::Test,
+class GoogleURLLoaderThrottleBoundSessionCredentialsTest
+ : public GoogleURLLoaderThrottleTest,
public ::testing::WithParamInterface<RequestAction> {
public:
- const GURL kTestGoogleURL = GURL("https://google.com");
- const GURL kGoogleSubdomainURL = GURL("https://accounts.google.com");
-
- GoogleURLLoaderThrottleTest() = default;
- ~GoogleURLLoaderThrottleTest() override = default;
+ GoogleURLLoaderThrottleBoundSessionCredentialsTest() = default;
+ ~GoogleURLLoaderThrottleBoundSessionCredentialsTest() override = default;
void ConfigureBoundSessionThrottlerParams(const std::string& domain,
const std::string& path,
@@ -115,15 +277,6 @@
return bound_session_handler_.get();
}
- GoogleURLLoaderThrottle* throttle() {
- if (!throttle_) {
- CreateThrottle();
- }
- return throttle_.get();
- }
-
- MockThrottleDelegate* delegate() { return delegate_.get(); }
-
void CallThrottleAndVerifyDeferExpectation(bool expect_defer,
const GURL& url) {
// `WillStartRequest()` has to be occur before `WillRedirectRequest()`, so
@@ -181,10 +334,11 @@
histogram_tester_ = std::make_unique<base::HistogramTester>();
}
- private:
- void CreateThrottle() {
+ protected:
+ void CreateThrottle() override {
chrome::mojom::DynamicParamsPtr dynamic_params(
chrome::mojom::DynamicParams::New());
+ dynamic_params->allowed_domains_for_apps = allowed_domains_for_apps_;
for (const auto& params : bound_session_throttler_params_) {
dynamic_params->bound_session_throttler_params.push_back(params->Clone());
}
@@ -203,33 +357,34 @@
throttle_->set_delegate(delegate_.get());
}
+ private:
base::test::ScopedFeatureList feature_list_{
switches::kEnableBoundSessionCredentials};
base::test::TaskEnvironment task_environment_;
raw_ptr<FakeBoundSessionRequestThrottledHandler, DanglingUntriaged>
bound_session_handler_ = nullptr;
- // Must outlive `throttle_`.
- std::unique_ptr<MockThrottleDelegate> delegate_;
- std::unique_ptr<GoogleURLLoaderThrottle> throttle_;
std::vector<BoundSessionThrottlerParamsPtr> bound_session_throttler_params_;
std::unique_ptr<base::HistogramTester> histogram_tester_ =
std::make_unique<base::HistogramTester>();
};
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusNullParams) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusNullParams) {
EXPECT_EQ(
GoogleURLLoaderThrottle::GetRequestBoundSessionStatus(kTestGoogleURL, {}),
RequestBoundSessionStatus::kNotCovered);
}
-TEST_F(GoogleURLLoaderThrottleTest, GetRequestBoundSessionStatusEmptyParams) {
+TEST_F(GoogleURLLoaderThrottleBoundSessionCredentialsTest,
+ GetRequestBoundSessionStatusEmptyParams) {
... (truncated)