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 <[email protected]> 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 <[email protected]> Commit-Queue: Samiya Caur <[email protected]> Reviewed-by: Maksim Sadym <[email protected]> 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