CVE-2026-11092
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BindRepeatingcontent/browser/devtools/browser_devtools_agent_host.cc |
modified | |
weak_factory_content/browser/devtools/devtools_url_loader_interceptor.cc |
modified | |
forcontent/browser/devtools/devtools_url_loader_interceptor.cc |
modified | |
ifcontent/browser/devtools/protocol/fetch_handler.cc |
modified | |
CanonicalCookiecontent/browser/devtools/protocol/fetch_handler.h |
modified | |
URLLoaderFactoryOverridecontent/browser/devtools/protocol/fetch_handler.h |
modified | |
DevToolsAgentHostClientcontent/browser/devtools/protocol/fetch_handler.h |
modified | |
DevToolsAgentHostImplcontent/browser/devtools/protocol/fetch_handler.h |
modified | |
DevToolsIOContextcontent/browser/devtools/protocol/fetch_handler.h |
modified | |
DevToolsURLLoaderInterceptorcontent/browser/devtools/protocol/fetch_handler.h |
modified |
Files Changed
chrome/browser/extensions/api/debugger/debugger_apitest.cccontent/browser/devtools/browser_devtools_agent_host.cccontent/browser/devtools/devtools_url_loader_interceptor.cccontent/browser/devtools/devtools_url_loader_interceptor.hcontent/browser/devtools/protocol/fetch_handler.cccontent/browser/devtools/protocol/fetch_handler.h
Patch
From 1ca1d66c8e3e24991fa91b4e370eed0f7c2bbc7b Mon Sep 17 00:00:00 2001
From: Alex Rudenko <alexrudenko@chromium.org>
Date: Mon, 27 Apr 2026 11:21:51 -0700
Subject: [PATCH] Ignore cookies for URLs restricted by extension policies
Bug: 500170887
Change-Id: I2cab568aef4df211e787179308d2ad49a91e919a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7795274
Commit-Queue: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Finnur Thorarinsson <finnur@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1621209}
---
diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
index f0e5623..4a1981c 100644
--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
@@ -918,8 +918,7 @@
found_allowed = true;
}
}
- // This assertion will be inverted in the next CL.
- EXPECT_TRUE(found_restricted) << "Restricted cookie was not found";
+ EXPECT_FALSE(found_restricted) << "Restricted cookie was found";
EXPECT_TRUE(found_allowed) << "Allowed cookie was not found";
}
diff --git a/content/browser/devtools/browser_devtools_agent_host.cc b/content/browser/devtools/browser_devtools_agent_host.cc
index 8ab309450e..948ed058 100644
--- a/content/browser/devtools/browser_devtools_agent_host.cc
+++ b/content/browser/devtools/browser_devtools_agent_host.cc
@@ -244,7 +244,7 @@
#endif
session->CreateAndAddHandler<protocol::IOHandler>(GetIOContext());
session->CreateAndAddHandler<protocol::FetchHandler>(
- GetIOContext(),
+ GetIOContext(), session->GetRootSession()->GetClient(),
base::BindRepeating([](base::OnceClosure cb) { std::move(cb).Run(); }));
session->CreateAndAddHandler<protocol::MemoryHandler>();
session->CreateAndAddHandler<protocol::SecurityHandler>();
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index 9bb7f80..ffdcc591 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -902,8 +902,11 @@
}
DevToolsURLLoaderInterceptor::DevToolsURLLoaderInterceptor(
- RequestInterceptedCallback callback)
- : request_intercepted_callback_(std::move(callback)), weak_factory_(this) {}
+ RequestInterceptedCallback callback,
+ CheckCookieAccessCallback cookie_access_callback)
+ : request_intercepted_callback_(std::move(callback)),
+ cookie_access_callback_(std::move(cookie_access_callback)),
+ weak_factory_(this) {}
DevToolsURLLoaderInterceptor::~DevToolsURLLoaderInterceptor() {
for (auto const& entry : jobs_)
@@ -1553,6 +1556,11 @@
},
base::BarrierClosure(cookies.size(), std::move(callback)));
for (auto& cookie : cookies) {
+ if (interceptor_ && interceptor_->cookie_access_callback_ &&
+ !interceptor_->cookie_access_callback_.Run(*cookie)) {
+ on_cookie_set.Run(net::CookieAccessResult());
+ continue;
+ }
cookie_manager_->SetCanonicalCookie(
*cookie, create_loader_params_->request.url, options, on_cookie_set);
}
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.h b/content/browser/devtools/devtools_url_loader_interceptor.h
index c2360e79..7c40f68b 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.h
+++ b/content/browser/devtools/devtools_url_loader_interceptor.h
@@ -178,12 +178,16 @@
using HandleAuthRequestCallback =
base::OnceCallback<void(bool use_fallback,
const std::optional<net::AuthCredentials>&)>;
+ using CheckCookieAccessCallback =
+ base::RepeatingCallback<bool(const net::CanonicalCookie&)>;
// Can only be called on the IO thread.
static void HandleAuthRequest(GlobalRequestID req_id,
const net::AuthChallengeInfo& auth_info,
HandleAuthRequestCallback callback);
- explicit DevToolsURLLoaderInterceptor(RequestInterceptedCallback callback);
+ explicit DevToolsURLLoaderInterceptor(
+ RequestInterceptedCallback callback,
+ CheckCookieAccessCallback cookie_access_callback = {});
DevToolsURLLoaderInterceptor(const DevToolsURLLoaderInterceptor&) = delete;
DevToolsURLLoaderInterceptor& operator=(const DevToolsURLLoaderInterceptor&) =
@@ -268,6 +272,7 @@
}
const RequestInterceptedCallback request_intercepted_callback_;
+ const CheckCookieAccessCallback cookie_access_callback_;
std::vector<Pattern> patterns_;
bool handle_auth_ = false;
diff --git a/content/browser/devtools/protocol/fetch_handler.cc b/content/browser/devtools/protocol/fetch_handler.cc
index 67834c8e..66e64598 100644
--- a/content/browser/devtools/protocol/fetch_handler.cc
+++ b/content/browser/devtools/protocol/fetch_handler.cc
@@ -6,6 +6,7 @@
#include <memory>
#include <utility>
+#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
@@ -33,15 +34,22 @@
FetchHandler::FetchHandler(
DevToolsIOContext* io_context,
+ DevToolsAgentHostClient* client,
UpdateLoaderFactoriesCallback update_loader_factories_callback,
base::OnceClosure cleanup_after_modifications_callback)
: DevToolsDomainHandler(Fetch::Metainfo::domainName),
io_context_(io_context),
update_loader_factories_callback_(
std::move(update_loader_factories_callback)),
+ client_(client),
cleanup_after_modifications_callback_(
std::move(cleanup_after_modifications_callback)) {}
+bool FetchHandler::CanAccessCookie(const net::CanonicalCookie& cookie) const {
+ return NetworkHandler::CanAccessCookie(CHECK_DEREF(client_.get()),
+ /*is_webui=*/false, cookie);
+}
+
FetchHandler::~FetchHandler() {
if (did_modifications_ && cleanup_after_modifications_callback_) {
std::move(cleanup_after_modifications_callback_).Run();
@@ -113,8 +121,15 @@
std::unique_ptr<EnableCallback> callback) {
if (!interceptor_) {
interceptor_ =
- std::make_unique<DevToolsURLLoaderInterceptor>(base::BindRepeating(
- &FetchHandler::RequestIntercepted, weak_factory_.GetWeakPtr()));
+ std::make_unique<DevToolsURLLoaderInterceptor>(
+ base::BindRepeating(&FetchHandler::RequestIntercepted,
+ weak_factory_.GetWeakPtr()),
+ base::BindRepeating(
+ [](base::WeakPtr<FetchHandler> weak_this,
+ const net::CanonicalCookie& cookie) {
+ return weak_this && weak_this->CanAccessCookie(cookie);
+ },
+ weak_factory_.GetWeakPtr()));
}
std::vector<DevToolsURLLoaderInterceptor::Pattern> interception_patterns;
Response response = ToInterceptionPatterns(patterns, &interception_patterns);
diff --git a/content/browser/devtools/protocol/fetch_handler.h b/content/browser/devtools/protocol/fetch_handler.h
index 964a92e7..8bab55e 100644
--- a/content/browser/devtools/protocol/fetch_handler.h
+++ b/content/browser/devtools/protocol/fetch_handler.h
@@ -14,6 +14,10 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
+namespace net {
+class CanonicalCookie;
+} // namespace net
+
namespace network {
namespace mojom {
class URLLoaderFactoryOverride;
@@ -22,6 +26,7 @@
} // namespace network
namespace content {
+class DevToolsAgentHostClient;
class DevToolsAgentHostImpl;
class DevToolsIOContext;
class DevToolsURLLoaderInterceptor;
@@ -36,6 +41,7 @@
base::RepeatingCallback<void(base::OnceClosure)>;
FetchHandler(DevToolsIOContext* io_context,
+ DevToolsAgentHostClient* client,
UpdateLoaderFactoriesCallback update_loader_factories_callback,
base::OnceClosure cleanup_after_modifications_callback =
base::OnceClosure());
@@ -111,12 +117,15 @@
mojo::ScopedDataPipeConsumerHandle pipe,
const std::string& mime_type);
+ bool CanAccessCookie(const net::CanonicalCookie& cookie) const;
+
void RequestIntercepted(std::unique_ptr<InterceptedRequestInfo> info);
const raw_ptr<DevToolsIOContext> io_context_;
std::unique_ptr<Fetch::Frontend> frontend_;
std::unique_ptr<DevToolsURLLoaderInterceptor> interceptor_;
UpdateLoaderFactoriesCallback update_loader_factories_callback_;
+ raw_ptr<DevToolsAgentHostClient> client_;
bool did_modifications_ = false;
base::OnceClosure cleanup_after_modifications_callback_;
base::WeakPtrFactory<FetchHandler> weak_factory_{this};
Regression Test / PoC
diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
index f0e5623..4a1981c 100644
--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
@@ -918,8 +918,7 @@
found_allowed = true;
}
}
- // This assertion will be inverted in the next CL.
- EXPECT_TRUE(found_restricted) << "Restricted cookie was not found";
+ EXPECT_FALSE(found_restricted) << "Restricted cookie was found";
EXPECT_TRUE(found_allowed) << "Allowed cookie was not found";
}
Original Bug Report
DevTools bypass in Fetch.fulfillRequest allows cookie injection on restricted hosts
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: The DevTools Fetch.fulfillRequest API fails to enforce MayAttachToURL permission checks when processing Set-Cookie headers. This potentially allows a malicious extension with the debugger permission to bypass security boundaries and inject HttpOnly cookies into restricted domains like the Chrome Web Store. While SameSite=Strict cookies are protected by context enforcement, SameSite=None cookies can be successfully injected.
Affected files:
content/browser/devtools/devtools_url_loader_interceptor.cccontent/browser/devtools/protocol/fetch_handler.cc
Estimated timestamp from git blame: 2024-08-20
Description
The DevTools Fetch domain lacks the strict cookie authorization checks that are correctly enforced in the Network domain. When a DevTools client (such as a Chrome extension) uses Fetch.fulfillRequest to mock a response, the browser processes any included Set-Cookie headers via InterceptionJob::ProcessSetCookies in content/browser/devtools/devtools_url_loader_interceptor.cc.
Unlike Network.setCookie, which verifies NetworkHandler::CanAccessCookie (and subsequently DevToolsAgentHostClient::MayAttachToURL), ProcessSetCookies performs no validation against the extension’s privileges for the target URL. Furthermore, it explicitly calls options.set_include_httponly(), granting the ability to write HttpOnly cookies using the browser process’s unrestricted CookieManager.
While the underlying cookie layer correctly enforces SameSite contexts—meaning cross-site injection of SameSite=Strict or Lax cookies will be rejected—an attacker can still successfully inject SameSite=None; Secure; HttpOnly cookies. By intercepting a subresource request from an allowed domain to a highly restricted target (like chromewebstore.google.com or an enterprise-policy-blocked host), the attacker can force the browser to store the injected cookie for the restricted domain.
Potential Reproduction Steps
Note: These are suggested theoretical steps based on source code analysis; a working proof-of-concept has not been executed.
- An attacker creates an extension with the
debuggerpermission and tricks a user into installing it. - The user navigates to an attacker-controlled page (e.g.,
https://attacker.example). - The malicious extension attaches the debugger to the current tab (
attacker.example) and enables theFetchdomain to intercept network requests. - The attacker’s webpage initiates a subresource request to a restricted domain, such as
fetch('https://chromewebstore.google.com/', {mode: 'no-cors'});. - The extension intercepts the
Fetch.requestPausedevent and responds usingFetch.fulfillRequestwith a mocked response containing:Set-Cookie: sid=evil; HttpOnly; Secure; SameSite=None; Path=/. - The browser processes the mocked response and successfully injects the
HttpOnlycookie intochromewebstore.google.com, bypassing theMayAttachToURLrestrictions.
Suggested Fix
In InterceptionJob::ProcessSetCookies (content/browser/devtools/devtools_url_loader_interceptor.cc), implement a permission check before calling cookie_manager_->SetCanonicalCookie. The code should verify that the DevTools client is permitted to interact with the target URL by checking DevToolsAgentHostClient::MayAttachToURL, matching the security posture of NetworkHandler::CanAccessCookie. If the client lacks permission, the Set-Cookie headers should be discarded.
Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.