Chrome · Extensions
CVE-2026-78960
Logic Error in Extensions
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fservices/network/public/cpp/cors/cors_unittest.cc |
modified | |
forservices/network/public/cpp/cors/cors_unittest.cc |
modified | |
forthird_party/blink/common/service_worker/service_worker_loader_helpers.cc |
modified |
Files Changed
services/network/public/cpp/cors/cors.ccservices/network/public/cpp/cors/cors.hservices/network/public/cpp/cors/cors_unittest.ccthird_party/blink/common/service_worker/service_worker_loader_helpers.ccthird_party/blink/renderer/platform/loader/cors/cors.ccthird_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
Patch
From 77d1561238e8a4d00c5c1068a6daf76dbca12a6f Mon Sep 17 00:00:00 2001
From: Maksim Sisov <msisov@igalia.com>
Date: Wed, 15 Jul 2026 05:01:24 -0700
Subject: [PATCH] Add network::cors::IsCorsSafelistedResponseHeaderName
The CORS-safelisted response-header-name list is duplicated in two Blink
copies: the renderer WTF predicate IsCorsSafelistedResponseHeader() and
the blink-common IsCorsExposedResponseHeader() helper. There is no
browser-process-callable primitive for it, which a later change needs.
Add IsCorsSafelistedResponseHeaderName() next to the request-side
safelist predicates in network::cors and fold both Blink copies onto it.
The blink-common copy keeps its content-range media carve-out as an
explicit branch. No behavior change: the renderer set was matched
case-insensitively before and the new predicate lowercases the input.
Bug: 533917984
Change-Id: I41ba7c40c3a4474c21e98bd8be998f538e4f3513
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8084477
Reviewed-by: Kenichi Ishibashi <bashi@chromium.org>
Commit-Queue: Maksim Sisov <msisov@igalia.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1662521}
---
diff --git a/services/network/public/cpp/cors/cors.cc b/services/network/public/cpp/cors/cors.cc
index 14334f7..ec75705 100644
--- a/services/network/public/cpp/cors/cors.cc
+++ b/services/network/public/cpp/cors/cors.cc
@@ -257,6 +257,21 @@
/*is_ad_auction_trusted_signals_request=*/false);
}
+bool IsCorsSafelistedResponseHeaderName(std::string_view name) {
+ // https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
+ static constexpr auto kSafelistedResponseHeaderNames =
+ base::MakeFixedFlatSet<std::string_view>({
+ "cache-control",
+ "content-language",
+ "content-length",
+ "content-type",
+ "expires",
+ "last-modified",
+ "pragma",
+ });
+ return kSafelistedResponseHeaderNames.contains(base::ToLowerASCII(name));
+}
+
bool IsCorsSafelistedHeader(const std::string& name,
const std::string& value,
bool is_ad_auction_trusted_signals_request) {
diff --git a/services/network/public/cpp/cors/cors.h b/services/network/public/cpp/cors/cors.h
index 8c086d5..6728fb6a 100644
--- a/services/network/public/cpp/cors/cors.h
+++ b/services/network/public/cpp/cors/cors.h
@@ -107,6 +107,12 @@
bool IsNoCorsSafelistedHeader(const std::string& name,
const std::string& value);
+// Returns true if `name` is a CORS-safelisted response header name.
+// The match is case-insensitive.
+// https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
+COMPONENT_EXPORT(NETWORK_CPP)
+bool IsCorsSafelistedResponseHeaderName(std::string_view name);
+
// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names
// |headers| must not contain multiple headers for the same name.
// The returned list is NOT sorted.
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc
index 991dfd7..590471ee1 100644
--- a/services/network/public/cpp/cors/cors_unittest.cc
+++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -218,6 +218,33 @@
EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo"));
}
+TEST_F(CorsTest, SafelistedResponseHeaderName) {
+ // The seven CORS-safelisted response header names, in mixed case to exercise
+ // the case-insensitive match.
+ static constexpr std::string_view kSafelisted[] = {
+ "cache-control", "Content-Language", "CONTENT-LENGTH", "content-type",
+ "Expires", "Last-Modified", "pragma",
+ };
+ for (std::string_view name : kSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_TRUE(IsCorsSafelistedResponseHeaderName(name));
+ }
+
+ // "content-range" is deliberately excluded: it is a media-only carve-out
+ // kept out of the JS-visible safelist. The rest are ordinary non-safelisted
+ // headers.
+ static constexpr std::string_view kNotSafelisted[] = {
+ "content-range",
+ "set-cookie",
+ "x-auth-token",
+ "sec-ch-ua",
+ };
+ for (std::string_view name : kNotSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_FALSE(IsCorsSafelistedResponseHeaderName(name));
+ }
+}
+
TEST_F(CorsTest, SafelistedAccept) {
EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html"));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html"));
diff --git a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
index f64bffbc..cf2a042 100644
--- a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
+++ b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
@@ -13,7 +13,6 @@
#include <vector>
#include "base/byte_size.h"
-#include "base/containers/fixed_flat_set.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
@@ -22,6 +21,7 @@
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/redirect_util.h"
+#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
@@ -33,30 +33,18 @@
namespace blink {
namespace {
-// LINT.IfChange(kCorsSafelistedResponseHeaderNames)
-// https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
-constexpr auto kCorsSafelistedResponseHeaderNames =
- base::MakeFixedFlatSet<std::string_view>({
- "cache-control",
- "content-language",
- "content-length",
- // "content-range" is not a standard CORS-safelisted response header,
- // but it is required by C++ media loaders (e.g. WebMediaPlayer) to
- // process "206 Partial Content" range responses. We permit it in
- // URLResponseHead to avoid breaking media playback, while it remains
- // filtered out and hidden from JavaScript's view in the renderer.
- "content-range",
- "content-type",
- "expires",
- "last-modified",
- "pragma",
- });
-// LINT.ThenChange(third_party/blink/renderer/platform/loader/cors/cors.cc:allowed_cross_origin_response_headers)
-
bool IsCorsExposedResponseHeader(
std::string_view name,
const std::vector<std::string>& cors_exposed_header_names) {
- if (kCorsSafelistedResponseHeaderNames.contains(base::ToLowerASCII(name))) {
+ if (network::cors::IsCorsSafelistedResponseHeaderName(name)) {
+ return true;
+ }
+ // "content-range" is not a standard CORS-safelisted response header, but it
+ // is required by C++ media loaders (e.g. WebMediaPlayer) to process "206
+ // Partial Content" range responses. We permit it in URLResponseHead to avoid
+ // breaking media playback, while it remains filtered out and hidden from
+ // JavaScript's view in the renderer.
+ if (base::ToLowerASCII(name) == "content-range") {
return true;
}
for (const auto& exposed : cors_exposed_header_names) {
diff --git a/third_party/blink/renderer/platform/loader/cors/cors.cc b/third_party/blink/renderer/platform/loader/cors/cors.cc
index 107209d2..60e2ae1 100644
--- a/third_party/blink/renderer/platform/loader/cors/cors.cc
+++ b/third_party/blink/renderer/platform/loader/cors/cors.cc
@@ -205,22 +205,7 @@
}
bool IsCorsSafelistedResponseHeader(const String& name) {
- // LINT.IfChange(allowed_cross_origin_response_headers)
- // https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
- // TODO(dcheng): Consider using a flat_set here with a transparent comparator.
- DEFINE_THREAD_SAFE_STATIC_LOCAL(HTTPHeaderSet,
- allowed_cross_origin_response_headers,
- ({
- "cache-control",
- "content-language",
- "content-length",
- "content-type",
- "expires",
- "last-modified",
- "pragma",
- }));
- // LINT.ThenChange(third_party/blink/common/service_worker/service_worker_loader_helpers.cc:kCorsSafelistedResponseHeaderNames)
- return allowed_cross_origin_response_headers.contains(name.Ascii());
+ return network::cors::IsCorsSafelistedResponseHeaderName(name.Latin1());
}
// In the spec, https://fetch.spec.whatwg.org/#ref-for-concept-request-mode,
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 5b56353d..c3e4b58 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -547,8 +547,8 @@
],
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc
index 991dfd7..590471ee1 100644
--- a/services/network/public/cpp/cors/cors_unittest.cc
+++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -218,6 +218,33 @@
EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo"));
}
+TEST_F(CorsTest, SafelistedResponseHeaderName) {
+ // The seven CORS-safelisted response header names, in mixed case to exercise
+ // the case-insensitive match.
+ static constexpr std::string_view kSafelisted[] = {
+ "cache-control", "Content-Language", "CONTENT-LENGTH", "content-type",
+ "Expires", "Last-Modified", "pragma",
+ };
+ for (std::string_view name : kSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_TRUE(IsCorsSafelistedResponseHeaderName(name));
+ }
+
+ // "content-range" is deliberately excluded: it is a media-only carve-out
+ // kept out of the JS-visible safelist. The rest are ordinary non-safelisted
+ // headers.
+ static constexpr std::string_view kNotSafelisted[] = {
+ "content-range",
+ "set-cookie",
+ "x-auth-token",
+ "sec-ch-ua",
+ };
+ for (std::string_view name : kNotSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_FALSE(IsCorsSafelistedResponseHeaderName(name));
+ }
+}
+
TEST_F(CorsTest, SafelistedAccept) {
EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html"));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html"));
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