Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in SignIn
DescriptionInformation leak in SignIn
ComponentSignIn
Bug ClassLogic Error
Tracker513608831
Fix commit49bb74f7f158 (chromium/src) +109/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
Value
google_apis/common/base_requests.h
modified
RequestSender
google_apis/common/base_requests.h
modified
TEST_F
google_apis/common/base_requests_unittest.cc
modified

Files Changed

  • google_apis/common/base_requests.cc
  • google_apis/common/base_requests.h
  • google_apis/common/base_requests_unittest.cc
From 49bb74f7f1589143f800972535ff296ad9dae6b9 Mon Sep 17 00:00:00 2001
From: Ming-Ying Chung <mych@chromium.org>
Date: Fri, 31 Jul 2026 00:28:10 -0700
Subject: [PATCH] [google_apis] Drop Authorization header on cross-origin redirects

`UrlFetchRequestBase` attaches an `Authorization` header to outgoing
Google API requests and executes them through `SimpleURLLoader`.
`SimpleURLLoader` forwards explicitly configured headers across HTTP
redirects. Consequently, if an API endpoint responds with a 3xx redirect
to a different origin, the access token is forwarded to the redirected
destination.

To prevent credential leakage, register a redirect callback on
`SimpleURLLoader` that checks if the redirect target is same-origin with
the previous URL. Cross-origin redirects append the `Authorization`
header to the list of headers to remove, while same-origin redirects
retain the header for existing workflows.

Bug: 513608831
Change-Id: I6b19f963c9ff3bb0b996b1fe29bbbe84d962d18e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8050627
Commit-Queue: Ming-Ying Chung <mych@chromium.org>
Reviewed-by: Eriko Kurimoto <elkurin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1671672}
---

diff --git a/google_apis/common/base_requests.cc b/google_apis/common/base_requests.cc
index 037901c..6268c47 100644
--- a/google_apis/common/base_requests.cc
+++ b/google_apis/common/base_requests.cc
@@ -30,8 +30,10 @@
 #include "net/http/http_request_headers.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
+#include "net/url_request/redirect_info.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(IS_POSIX)
 #include <fcntl.h>
@@ -308,9 +310,24 @@
   url_loader_->SetOnResponseStartedCallback(base::BindOnce(
       &UrlFetchRequestBase::OnResponseStarted, weak_ptr_factory_.GetWeakPtr()));
 
+  url_loader_->SetOnRedirectCallback(base::BindRepeating(
+      &UrlFetchRequestBase::OnRedirect, weak_ptr_factory_.GetWeakPtr()));
+
   url_loader_->DownloadAsStream(sender_->url_loader_factory(), this);
 }
 
+void UrlFetchRequestBase::OnRedirect(
+    const GURL& url_before_redirect,
+    const net::RedirectInfo& redirect_info,
+    const network::mojom::URLResponseHead& response_head,
+    std::vector<std::string>* to_be_removed_headers) {
+  // Strip the Authorization header on cross-origin redirects to prevent
+  // sensitive access tokens from leaking to third-party destinations.
+  if (!url::IsSameOriginWith(url_before_redirect, redirect_info.new_url)) {
+    to_be_removed_headers->push_back(net::HttpRequestHeaders::kAuthorization);
+  }
+}
+
 void UrlFetchRequestBase::OnDownloadProgress(ProgressCallback progress_callback,
                                              uint64_t current) {
   progress_callback.Run(static_cast<int64_t>(current),
diff --git a/google_apis/common/base_requests.h b/google_apis/common/base_requests.h
index 7e0dda0..605a72a 100644
--- a/google_apis/common/base_requests.h
+++ b/google_apis/common/base_requests.h
@@ -32,6 +32,10 @@
 class Value;
 }  // namespace base
 
+namespace net {
+struct RedirectInfo;
+}  // namespace net
+
 namespace google_apis {
 
 class RequestSender;
@@ -258,6 +262,12 @@
   void OnResponseStarted(const GURL& final_url,
                          const network::mojom::URLResponseHead& response_head);
 
+  // Called when SimpleURLLoader encounters a redirect.
+  void OnRedirect(const GURL& url_before_redirect,
+                  const net::RedirectInfo& redirect_info,
+                  const network::mojom::URLResponseHead& response_head,
+                  std::vector<std::string>* to_be_removed_headers);
+
   // Invokes callback with |code| and request to delete the request to
   // |sender_|.
   void CompleteRequestWithError(ApiErrorCode code);
diff --git a/google_apis/common/base_requests_unittest.cc b/google_apis/common/base_requests_unittest.cc
index 3892cff4..7fc435d40 100644
--- a/google_apis/common/base_requests_unittest.cc
+++ b/google_apis/common/base_requests_unittest.cc
@@ -18,7 +18,9 @@
 #include "google_apis/common/test_util.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/http/http_request_headers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/network_service.h"
@@ -129,16 +131,36 @@
     task_environment_.RunUntilIdle();
   }
 
+  // Handles incoming HTTP requests on the primary test server. Records each
+  // request to verify header delivery and issues a 302 redirect for the
+  // `/redirect` path if a target URL is configured.
   std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
       const net::test_server::HttpRequest& request) {
-    std::unique_ptr<net::test_server::BasicHttpResponse> response(
-        new net::test_server::BasicHttpResponse);
+    received_requests_.push_back(request);
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    if (redirect_location_.is_valid() && request.relative_url == "/redirect") {
+      response->set_code(net::HTTP_FOUND);
+      response->AddCustomHeader("Location", redirect_location_.spec());
+      return std::move(response);
+    }
     response->set_code(response_code_);
     response->set_content(response_body_);
     response->set_content_type("application/json");
     return std::move(response);
   }
 
+  // Handles incoming HTTP requests on the secondary cross-origin server.
+  // Records received requests to inspect headers delivered across origin
+  // boundaries.
+  std::unique_ptr<net::test_server::HttpResponse> HandleOtherOriginRequest(
+      const net::test_server::HttpRequest& request) {
+    other_origin_requests_.push_back(request);
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_OK);
+    response->set_content_type("application/json");
+    return std::move(response);
+  }
+
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO};
   std::unique_ptr<network::mojom::NetworkService> network_service_;
@@ -148,9 +170,13 @@
       test_shared_loader_factory_;
   std::unique_ptr<RequestSender> sender_;
   net::EmbeddedTestServer test_server_;
+  net::EmbeddedTestServer other_origin_server_;
 
   net::HttpStatusCode response_code_;
   std::string response_body_;
+  GURL redirect_location_;
+  std::vector<net::test_server::HttpRequest> received_requests_;
+  std::vector<net::test_server::HttpRequest> other_origin_requests_;
 };
 
 TEST_F(BaseRequestsTest, ParseValidJson) {
@@ -198,6 +224,60 @@
   EXPECT_EQ(HTTP_SERVICE_UNAVAILABLE, error);
 }
 
+// Verifies that the Authorization header is removed when a request is
+// redirected to a cross-origin destination.
+TEST_F(BaseRequestsTest, AuthorizationHeaderRemovedOnCrossOriginRedirect) {
+  other_origin_server_.RegisterRequestHandler(base::BindRepeating(
+      &BaseRequestsTest::HandleOtherOriginRequest, base::Unretained(this)));
+  ASSERT_TRUE(other_origin_server_.Start());
+  ASSERT_NE(test_server_.base_url(), other_origin_server_.base_url());
+
+  redirect_location_ = other_origin_server_.GetURL("/target");
+
+  ApiErrorCode error = OTHER_ERROR;
+  base::RunLoop run_loop;
+  sender_->StartRequestWithAuthRetry(std::make_unique<FakeUrlFetchRequest>(
+      sender_.get(),
+      test_util::CreateQuitCallback(
+          &run_loop, test_util::CreateCopyResultCallback(&error)),
+      test_server_.GetURL("/redirect")));
+  run_loop.Run();
+
+  EXPECT_EQ(HTTP_SUCCESS, error);
+  ASSERT_EQ(1u, received_requests_.size());
+  EXPECT_NE(received_requests_[0].headers.end(),
+            received_requests_[0].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+  ASSERT_EQ(1u, other_origin_requests_.size());
+  EXPECT_EQ(other_origin_requests_[0].headers.end(),
+            other_origin_requests_[0].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+}
+
+// Verifies that the Authorization header is preserved when a request is
+// redirected to a same-origin destination.
+TEST_F(BaseRequestsTest, AuthorizationHeaderKeptOnSameOriginRedirect) {
+  redirect_location_ = test_server_.GetURL("/target");
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/google_apis/common/base_requests_unittest.cc b/google_apis/common/base_requests_unittest.cc
index 3892cff4..7fc435d40 100644
--- a/google_apis/common/base_requests_unittest.cc
+++ b/google_apis/common/base_requests_unittest.cc
@@ -18,7 +18,9 @@
 #include "google_apis/common/test_util.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/http/http_request_headers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/network_service.h"
@@ -129,16 +131,36 @@
     task_environment_.RunUntilIdle();
   }
 
+  // Handles incoming HTTP requests on the primary test server. Records each
+  // request to verify header delivery and issues a 302 redirect for the
+  // `/redirect` path if a target URL is configured.
   std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
       const net::test_server::HttpRequest& request) {
-    std::unique_ptr<net::test_server::BasicHttpResponse> response(
-        new net::test_server::BasicHttpResponse);
+    received_requests_.push_back(request);
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    if (redirect_location_.is_valid() && request.relative_url == "/redirect") {
+      response->set_code(net::HTTP_FOUND);
+      response->AddCustomHeader("Location", redirect_location_.spec());
+      return std::move(response);
+    }
     response->set_code(response_code_);
     response->set_content(response_body_);
     response->set_content_type("application/json");
     return std::move(response);
   }
 
+  // Handles incoming HTTP requests on the secondary cross-origin server.
+  // Records received requests to inspect headers delivered across origin
+  // boundaries.
+  std::unique_ptr<net::test_server::HttpResponse> HandleOtherOriginRequest(
+      const net::test_server::HttpRequest& request) {
+    other_origin_requests_.push_back(request);
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_OK);
+    response->set_content_type("application/json");
+    return std::move(response);
+  }
+
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO};
   std::unique_ptr<network::mojom::NetworkService> network_service_;
@@ -148,9 +170,13 @@
       test_shared_loader_factory_;
   std::unique_ptr<RequestSender> sender_;
   net::EmbeddedTestServer test_server_;
+  net::EmbeddedTestServer other_origin_server_;
 
   net::HttpStatusCode response_code_;
   std::string response_body_;
+  GURL redirect_location_;
+  std::vector<net::test_server::HttpRequest> received_requests_;
+  std::vector<net::test_server::HttpRequest> other_origin_requests_;
 };
 
 TEST_F(BaseRequestsTest, ParseValidJson) {
@@ -198,6 +224,60 @@
   EXPECT_EQ(HTTP_SERVICE_UNAVAILABLE, error);
 }
 
+// Verifies that the Authorization header is removed when a request is
+// redirected to a cross-origin destination.
+TEST_F(BaseRequestsTest, AuthorizationHeaderRemovedOnCrossOriginRedirect) {
+  other_origin_server_.RegisterRequestHandler(base::BindRepeating(
+      &BaseRequestsTest::HandleOtherOriginRequest, base::Unretained(this)));
+  ASSERT_TRUE(other_origin_server_.Start());
+  ASSERT_NE(test_server_.base_url(), other_origin_server_.base_url());
+
+  redirect_location_ = other_origin_server_.GetURL("/target");
+
+  ApiErrorCode error = OTHER_ERROR;
+  base::RunLoop run_loop;
+  sender_->StartRequestWithAuthRetry(std::make_unique<FakeUrlFetchRequest>(
+      sender_.get(),
+      test_util::CreateQuitCallback(
+          &run_loop, test_util::CreateCopyResultCallback(&error)),
+      test_server_.GetURL("/redirect")));
+  run_loop.Run();
+
+  EXPECT_EQ(HTTP_SUCCESS, error);
+  ASSERT_EQ(1u, received_requests_.size());
+  EXPECT_NE(received_requests_[0].headers.end(),
+            received_requests_[0].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+  ASSERT_EQ(1u, other_origin_requests_.size());
+  EXPECT_EQ(other_origin_requests_[0].headers.end(),
+            other_origin_requests_[0].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+}
+
+// Verifies that the Authorization header is preserved when a request is
+// redirected to a same-origin destination.
+TEST_F(BaseRequestsTest, AuthorizationHeaderKeptOnSameOriginRedirect) {
+  redirect_location_ = test_server_.GetURL("/target");
+
+  ApiErrorCode error = OTHER_ERROR;
+  base::RunLoop run_loop;
+  sender_->StartRequestWithAuthRetry(std::make_unique<FakeUrlFetchRequest>(
+      sender_.get(),
+      test_util::CreateQuitCallback(
+          &run_loop, test_util::CreateCopyResultCallback(&error)),
+      test_server_.GetURL("/redirect")));
+  run_loop.Run();
+
+  EXPECT_EQ(HTTP_SUCCESS, error);
+  ASSERT_EQ(2u, received_requests_.size());
+  EXPECT_NE(received_requests_[0].headers.end(),
+            received_requests_[0].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+  EXPECT_NE(received_requests_[1].headers.end(),
+            received_requests_[1].headers.find(
+                net::HttpRequestHeaders::kAuthorization));
+}
+
 TEST(BaseRequestsHttpRequestMethodEnumTest, ConvertsToString) {
   EXPECT_EQ(HttpRequestMethodToString(HttpRequestMethod::kGet), "GET");
   EXPECT_EQ(HttpRequestMethodToString(HttpRequestMethod::kPost), "POST");
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.