Chrome · SignIn
CVE-2026-79122
Logic Error in SignIn
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
Valuegoogle_apis/common/base_requests.h |
modified | |
RequestSendergoogle_apis/common/base_requests.h |
modified | |
TEST_Fgoogle_apis/common/base_requests_unittest.cc |
modified |
Files Changed
google_apis/common/base_requests.ccgoogle_apis/common/base_requests.hgoogle_apis/common/base_requests_unittest.cc
Patch
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.
References
On This Page