Chrome · DevTools
CVE-2026-106362
Authorization Error in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fcontent/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc |
modified | |
BindRepeatingcontent/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc |
modified |
Files Changed
third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txtthird_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.jscontent/browser/devtools/protocol/devtools_network_resource_loader.hcontent/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cccontent/browser/devtools/protocol/network_handler.cccontent/browser/devtools/protocol/devtools_network_resource_loader.cc
Patch
From 78293d960829a772cb29d539bee35edeb23d5edc Mon Sep 17 00:00:00 2001
From: Samiya Caur <samiyac@chromium.org>
Date: Tue, 08 Sep 2026 04:54:03 -0700
Subject: [PATCH] Enforce CSP on redirects in loadNetworkResource
Fixed: 514456975
Change-Id: I5c880abb89bedd2d3190de7afab42482dd242a6d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8322187
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Commit-Queue: Samiya Caur <samiyac@chromium.org>
Reviewed-by: Maksim Sadym <sadym@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1693703}
---
diff --git a/content/browser/devtools/protocol/devtools_network_resource_loader.cc b/content/browser/devtools/protocol/devtools_network_resource_loader.cc
index 73e6aa1..6cca7077 100644
--- a/content/browser/devtools/protocol/devtools_network_resource_loader.cc
+++ b/content/browser/devtools/protocol/devtools_network_resource_loader.cc
@@ -11,7 +11,9 @@
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
@@ -22,11 +24,13 @@
network::ResourceRequest resource_request,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory,
- CompletionCallback completion_callback)
+ CompletionCallback completion_callback,
+ RedirectCheckCallback redirect_check_callback)
: resource_request_(std::move(resource_request)),
traffic_annotation_(traffic_annotation),
url_loader_factory_(std::move(url_loader_factory)),
- completion_callback_(std::move(completion_callback)) {
+ completion_callback_(std::move(completion_callback)),
+ redirect_check_callback_(std::move(redirect_check_callback)) {
DownloadAsStream();
}
@@ -47,7 +51,8 @@
Caching caching,
Credentials include_credentials,
CompletionCallback completion_callback,
- bool is_outermost_main_frame) {
+ bool is_outermost_main_frame,
+ RedirectCheckCallback redirect_check_callback) {
network::ResourceRequest resource_request;
resource_request.url = std::move(gurl);
resource_request.request_initiator = origin;
@@ -90,7 +95,8 @@
return base::WrapUnique(new DevToolsNetworkResourceLoader(
std::move(resource_request), traffic_annotation,
- std::move(url_loader_factory), std::move(completion_callback)));
+ std::move(url_loader_factory), std::move(completion_callback),
+ std::move(redirect_check_callback)));
}
void DevToolsNetworkResourceLoader::OnRetry(base::OnceClosure start_retry) {
@@ -102,9 +108,27 @@
loader_ = network::SimpleURLLoader::Create(
std::make_unique<network::ResourceRequest>(resource_request_),
traffic_annotation_);
+ loader_->SetOnRedirectCallback(base::BindRepeating(
+ &DevToolsNetworkResourceLoader::OnRedirect, base::Unretained(this)));
loader_->DownloadAsStream(url_loader_factory_.get(), this);
}
+void DevToolsNetworkResourceLoader::OnRedirect(
+ const GURL& /*url_before_redirect*/,
+ const net::RedirectInfo& redirect_info,
+ const network::mojom::URLResponseHead& response_head,
+ std::vector<std::string>* /*removed_headers*/) {
+ if (redirect_check_callback_ &&
+ !redirect_check_callback_.Run(redirect_info)) {
+ const net::HttpResponseHeaders* response_headers =
+ response_head.headers.get();
+ loader_.reset();
+ std::move(completion_callback_)
+ .Run(this, response_headers, /*success=*/false, net::ERR_BLOCKED_BY_CSP,
+ "");
+ }
+}
+
void DevToolsNetworkResourceLoader::OnDataReceived(std::string_view chunk,
base::OnceClosure resume) {
content_.append(chunk);
diff --git a/content/browser/devtools/protocol/devtools_network_resource_loader.h b/content/browser/devtools/protocol/devtools_network_resource_loader.h
index 47d2588..58b3220ef 100644
--- a/content/browser/devtools/protocol/devtools_network_resource_loader.h
+++ b/content/browser/devtools/protocol/devtools_network_resource_loader.h
@@ -8,8 +8,10 @@
#include <memory>
#include <string_view>
+#include "base/functional/callback.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/remote.h"
+#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
@@ -33,6 +35,8 @@
bool success,
int net_error,
std::string content)>;
+ using RedirectCheckCallback =
+ base::RepeatingCallback<bool(const net::RedirectInfo& redirect_info)>;
enum class Caching { kBypass, kDefault };
enum class Credentials { kInclude, kSameSite };
@@ -48,7 +52,8 @@
Caching caching,
Credentials include_credentials,
CompletionCallback complete_callback,
- bool is_outermost_main_frame = false);
+ bool is_outermost_main_frame = false,
+ RedirectCheckCallback redirect_check_callback = {});
~DevToolsNetworkResourceLoader() override;
@@ -62,7 +67,8 @@
network::ResourceRequest resource_request,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory,
- CompletionCallback complete_callback);
+ CompletionCallback complete_callback,
+ RedirectCheckCallback redirect_check_callback);
void DownloadAsStream();
void OnDataReceived(std::string_view chunk,
@@ -72,11 +78,17 @@
void OnRetry(base::OnceClosure start_retry) override;
+ void OnRedirect(const GURL& url_before_redirect,
+ const net::RedirectInfo& redirect_info,
+ const network::mojom::URLResponseHead& response_head,
+ std::vector<std::string>* removed_headers);
+
const network::ResourceRequest resource_request_;
const net::NetworkTrafficAnnotationTag traffic_annotation_;
std::unique_ptr<network::SimpleURLLoader> loader_;
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
CompletionCallback completion_callback_;
+ RedirectCheckCallback redirect_check_callback_;
std::string content_;
};
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 9afa1f2b..73dcf73 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -4197,6 +4197,12 @@
return;
}
+ if (!frame->policy_container_host()) {
+ callback->sendFailure(
+ Response::ServerError("Frame does not have a policy container"));
+ return;
+ }
+
RenderFrameHostCSPContext csp_context(frame);
network::CSPCheckResult result = csp_context.IsAllowedByCsp(
@@ -4230,12 +4236,34 @@
return;
}
+ auto redirect_check = base::BindRepeating(
+ [](scoped_refptr<PolicyContainerHost> policy_container_host,
+ base::WeakPtr<RenderFrameHostImpl> frame, const GURL& initial_url,
+ const net::RedirectInfo& redirect_info) -> bool {
+ if (!frame || !policy_container_host ||
+ frame->policy_container_host() != policy_container_host.get()) {
+ return false;
+ }
+ RenderFrameHostCSPContext csp_context(frame.get());
+ network::CSPCheckResult result = csp_context.IsAllowedByCsp(
+ policy_container_host->policies().content_security_policies,
+ network::mojom::CSPDirectiveName::ConnectSrc,
+ redirect_info.new_url, initial_url,
+ /*has_followed_redirect=*/true,
+ /*source_location=*/nullptr,
+ network::CSPContext::CHECK_ENFORCED_CSP,
+ /*is_opaque_fenced_frame=*/false);
+ return result.IsAllowed();
+ },
+ base::WrapRefCounted(frame->policy_container_host()),
+ frame->GetWeakPtr(), gurl);
+
url_loader_factory.Bind(std::move(factory));
auto loader = DevToolsNetworkResourceLoader::Create(
std::move(url_loader_factory), std::move(gurl),
frame->GetLastCommittedOrigin(), frame->ComputeSiteForCookies(),
caching, include_credentials, std::move(complete_callback),
- frame->IsOutermostMainFrame());
+ frame->IsOutermostMainFrame(), std::move(redirect_check));
loaders_.emplace(std::move(loader), std::move(callback));
return;
}
diff --git a/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc b/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
index a226fce5..bf5c9d90 100644
--- a/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
@@ -136,13 +136,16 @@
std::unique_ptr<protocol::DevToolsNetworkResourceLoader> CreateLoader(
GURL url,
protocol::DevToolsNetworkResourceLoader::Caching caching,
- protocol::DevToolsNetworkResourceLoader::CompletionCallback callback) {
+ protocol::DevToolsNetworkResourceLoader::CompletionCallback callback,
+ protocol::DevToolsNetworkResourceLoader::RedirectCheckCallback
+ redirect_check_callback = {}) {
return protocol::DevToolsNetworkResourceLoader::Create(
CreateURLLoaderFactory(), std::move(url),
current_frame_host()->GetLastCommittedOrigin(),
current_frame_host()->ComputeSiteForCookies(), caching,
protocol::DevToolsNetworkResourceLoader::Credentials::kInclude,
- std::move(callback), current_frame_host()->IsOutermostMainFrame());
+ std::move(callback), current_frame_host()->IsOutermostMainFrame(),
+ std::move(redirect_check_callback));
}
protected:
@@ -302,4 +305,47 @@
EXPECT_FALSE(request->load_flags & net::LOAD_BYPASS_CACHE);
}
+IN_PROC_BROWSER_TEST_F(DevtoolsNetworkResourceLoaderTest, RedirectAllowed) {
+ base::RunLoop run_loop;
+ const GURL source_map_url(
+ embedded_test_server()->GetURL("a.com", "/devtools/source.map"));
+ const GURL redirect_url(embedded_test_server()->GetURL(
+ "a.com", "/server-redirect?" + source_map_url.spec()));
+
+ auto loader = CreateLoader(
+ redirect_url, protocol::DevToolsNetworkResourceLoader::Caching::kDefault,
+ base::BindOnce(CheckSuccess, this, &run_loop),
+ base::BindRepeating([](const net::RedirectInfo&) { return true; }));
+ run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DevtoolsNetworkResourceLoaderTest,
+ RedirectBlockedByCallback) {
+ base::RunLoop run_loop;
+ const GURL source_map_url(
+ embedded_test_server()->GetURL("a.com", "/devtools/source.map"));
+ const GURL redirect_url(embedded_test_server()->GetURL(
+ "a.com", "/server-redirect?" + source_map_url.spec()));
+
+ auto complete_callback = base::BindOnce(
+ [](base::RunLoop* run_loop,
+ protocol::DevToolsNetworkResourceLoader* loader,
+ const net::HttpResponseHeaders* rh, bool success, int net_error,
+ std::string content) {
+ EXPECT_FALSE(success);
+ EXPECT_EQ(net_error, net::ERR_BLOCKED_BY_CSP);
+ EXPECT_EQ(content, "");
+ EXPECT_TRUE(rh);
+ EXPECT_EQ(rh->response_code(), 301);
+ run_loop->Quit();
+ },
+ &run_loop);
+
+ auto loader = CreateLoader(
+ redirect_url, protocol::DevToolsNetworkResourceLoader::Caching::kDefault,
+ std::move(complete_callback),
+ base::BindRepeating([](const net::RedirectInfo&) { return false; }));
+ run_loop.Run();
+}
+
} // namespace content
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
index 7c31915..4508dc0a 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
@@ -11,4 +11,21 @@
success : true
}
}
+Response for fetch with redirect to same-origin resource (should be allowed by CSP): {
+ resource : {
+ headers : <object>
+ httpStatusCode : 200
+ stream : 2
+ success : true
+ }
+}
+Response for fetch with redirect to cross-origin resource (should be blocked by CSP): {
+ resource : {
+ headers : <object>
+ httpStatusCode : 302
+ netError : -30
+ netErrorName : net::ERR_BLOCKED_BY_CSP
+ success : false
+ }
+}
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
index 1fc7c8e0..f91479db 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
@@ -27,5 +27,17 @@
sameOriginUrl,
`Response for fetch with same-origin resource (should be allowed by CSP):`);
+ const redirectSameOriginUrl =
+ `http://127.0.0.1:8000/resources/redirect.php?url=http://127.0.0.1:8000/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ redirectSameOriginUrl,
+ `Response for fetch with redirect to same-origin resource (should be allowed by CSP):`);
+
+ const redirectCrossOriginUrl =
+ `http://127.0.0.1:8000/resources/redirect.php?url=https://localhost:8443/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ redirectCrossOriginUrl,
+ `Response for fetch with redirect to cross-origin resource (should be blocked by CSP):`);
+
testRunner.completeTest();
})
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc b/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
index a226fce5..bf5c9d90 100644
--- a/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_network_resource_loader_browsertest.cc
@@ -136,13 +136,16 @@
std::unique_ptr<protocol::DevToolsNetworkResourceLoader> CreateLoader(
GURL url,
protocol::DevToolsNetworkResourceLoader::Caching caching,
- protocol::DevToolsNetworkResourceLoader::CompletionCallback callback) {
+ protocol::DevToolsNetworkResourceLoader::CompletionCallback callback,
+ protocol::DevToolsNetworkResourceLoader::RedirectCheckCallback
+ redirect_check_callback = {}) {
return protocol::DevToolsNetworkResourceLoader::Create(
CreateURLLoaderFactory(), std::move(url),
current_frame_host()->GetLastCommittedOrigin(),
current_frame_host()->ComputeSiteForCookies(), caching,
protocol::DevToolsNetworkResourceLoader::Credentials::kInclude,
- std::move(callback), current_frame_host()->IsOutermostMainFrame());
+ std::move(callback), current_frame_host()->IsOutermostMainFrame(),
+ std::move(redirect_check_callback));
}
protected:
@@ -302,4 +305,47 @@
EXPECT_FALSE(request->load_flags & net::LOAD_BYPASS_CACHE);
}
+IN_PROC_BROWSER_TEST_F(DevtoolsNetworkResourceLoaderTest, RedirectAllowed) {
+ base::RunLoop run_loop;
+ const GURL source_map_url(
+ embedded_test_server()->GetURL("a.com", "/devtools/source.map"));
+ const GURL redirect_url(embedded_test_server()->GetURL(
+ "a.com", "/server-redirect?" + source_map_url.spec()));
+
+ auto loader = CreateLoader(
+ redirect_url, protocol::DevToolsNetworkResourceLoader::Caching::kDefault,
+ base::BindOnce(CheckSuccess, this, &run_loop),
+ base::BindRepeating([](const net::RedirectInfo&) { return true; }));
+ run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DevtoolsNetworkResourceLoaderTest,
+ RedirectBlockedByCallback) {
+ base::RunLoop run_loop;
+ const GURL source_map_url(
+ embedded_test_server()->GetURL("a.com", "/devtools/source.map"));
+ const GURL redirect_url(embedded_test_server()->GetURL(
+ "a.com", "/server-redirect?" + source_map_url.spec()));
+
+ auto complete_callback = base::BindOnce(
+ [](base::RunLoop* run_loop,
+ protocol::DevToolsNetworkResourceLoader* loader,
+ const net::HttpResponseHeaders* rh, bool success, int net_error,
+ std::string content) {
+ EXPECT_FALSE(success);
+ EXPECT_EQ(net_error, net::ERR_BLOCKED_BY_CSP);
+ EXPECT_EQ(content, "");
+ EXPECT_TRUE(rh);
+ EXPECT_EQ(rh->response_code(), 301);
+ run_loop->Quit();
+ },
+ &run_loop);
+
+ auto loader = CreateLoader(
+ redirect_url, protocol::DevToolsNetworkResourceLoader::Caching::kDefault,
+ std::move(complete_callback),
+ base::BindRepeating([](const net::RedirectInfo&) { return false; }));
+ run_loop.Run();
+}
+
} // namespace content
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
index 7c31915..4508dc0a 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
@@ -11,4 +11,21 @@
success : true
}
}
+Response for fetch with redirect to same-origin resource (should be allowed by CSP): {
+ resource : {
+ headers : <object>
+ httpStatusCode : 200
+ stream : 2
+ success : true
+ }
+}
+Response for fetch with redirect to cross-origin resource (should be blocked by CSP): {
+ resource : {
+ headers : <object>
+ httpStatusCode : 302
+ netError : -30
+ netErrorName : net::ERR_BLOCKED_BY_CSP
+ success : false
+ }
+}
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
index 1fc7c8e0..f91479db 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
@@ -27,5 +27,17 @@
sameOriginUrl,
`Response for fetch with same-origin resource (should be allowed by CSP):`);
+ const redirectSameOriginUrl =
+ `http://127.0.0.1:8000/resources/redirect.php?url=http://127.0.0.1:8000/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ redirectSameOriginUrl,
+ `Response for fetch with redirect to same-origin resource (should be allowed by CSP):`);
+
+ const redirectCrossOriginUrl =
+ `http://127.0.0.1:8000/resources/redirect.php?url=https://localhost:8443/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ redirectCrossOriginUrl,
+ `Response for fetch with redirect to cross-origin resource (should be blocked by CSP):`);
+
testRunner.completeTest();
})
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