CVE-2026-11658
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifextensions/browser/api/web_request/web_request_api.cc |
modified | |
navigation_response_task_runner_extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc |
modified |
Files Changed
extensions/browser/api/web_request/web_request_api.ccextensions/browser/api/web_request/web_request_api.hextensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
Patch
From ef7d0a803c37c6efe4d4278ab1aadf205b94271a Mon Sep 17 00:00:00 2001
From: Andrea Orru <andreaorru@chromium.org>
Date: Fri, 24 Jul 2026 15:27:02 -0700
Subject: [PATCH] [Extensions] Rename request IDs in WebRequestProxyingURLLoaderFactory
Disambiguate request ID parameters and members in WebRequestAPI and
WebRequestProxyingURLLoaderFactory:
- Rename `client_request_id` to `request_id_from_client` to clearly
denote that the ID is supplied by the upstream caller of
`CreateLoaderAndStart()`, avoiding ambiguity with downstream
interfaces such as `URLLoaderClient`.
- Rename `network_service_request_id` to
`request_id_for_network_service` to make explicit that this ID is
assigned by the proxy and forwarded downstream to the network service.
- Rename `request_id` to `browser_context_request_id` to make explicit
that this ID is unique per BrowserContext and avoid ambiguity with
other shorthanded request IDs.
Bug: 513564337, 521887333
Change-Id: I47dfafbf798bbe0506c1564461f588434819580d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8134541
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Commit-Queue: Andrea Orru <andreaorru@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668200}
---
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 19954b17..8c1e8c3 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -389,10 +389,11 @@
WebRequestAPI::RequestIDGenerator::RequestIDGenerator() = default;
WebRequestAPI::RequestIDGenerator::~RequestIDGenerator() = default;
-int64_t WebRequestAPI::RequestIDGenerator::Generate(int32_t routing_id,
- int32_t client_request_id) {
+int64_t WebRequestAPI::RequestIDGenerator::Generate(
+ int32_t routing_id,
+ int32_t request_id_from_client) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- auto it = saved_id_map_.find({routing_id, client_request_id});
+ auto it = saved_id_map_.find({routing_id, request_id_from_client});
if (it != saved_id_map_.end()) {
int64_t id = it->second;
saved_id_map_.erase(it);
@@ -402,12 +403,12 @@
}
void WebRequestAPI::RequestIDGenerator::SaveID(int32_t routing_id,
- int32_t client_request_id,
+ int32_t request_id_from_client,
uint64_t request_id) {
- // If `client_request_id` is 0, we cannot reliably match the generated ID to a
- // restarted request, so ignore it.
- if (client_request_id != 0) {
- saved_id_map_.insert({{routing_id, client_request_id}, request_id});
+ // If `request_id_from_client` is 0, we cannot reliably match the generated
+ // ID to a restarted request, so ignore it.
+ if (request_id_from_client != 0) {
+ saved_id_map_.insert({{routing_id, request_id_from_client}, request_id});
}
}
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h
index 76b78788..9832015 100644
--- a/extensions/browser/api/web_request/web_request_api.h
+++ b/extensions/browser/api/web_request/web_request_api.h
@@ -166,15 +166,16 @@
~RequestIDGenerator();
// Generates a WebRequest ID. If `SaveID()` was previously called with the
- // same (`routing_id`, `client_request_id`) pair, returns the saved ID and
- // removes the mapping. Otherwise, generates and returns a new unique ID.
- int64_t Generate(int32_t routing_id, int32_t client_request_id);
+ // same (`routing_id`, `request_id_from_client`) pair, returns the saved ID
+ // and removes the mapping. Otherwise, generates and returns a new unique
+ // ID.
+ int64_t Generate(int32_t routing_id, int32_t request_id_from_client);
- // Maps a WebRequest ID to a (`routing_id`, `client_request_id`) pair when a
- // request is restarted. Callers must subsequently call `Generate()` with
- // the same pair to reclaim the ID and prevent memory leaks.
+ // Maps a WebRequest ID to a (`routing_id`, `request_id_from_client`) pair
+ // when a request is restarted. Callers must subsequently call `Generate()`
+ // with the same pair to reclaim the ID and prevent memory leaks.
void SaveID(int32_t routing_id,
- int32_t client_request_id,
+ int32_t request_id_from_client,
uint64_t request_id);
// Generates a non-zero request ID to forward to the network service for
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index cf5998e2..ac1f5c0f 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -141,9 +141,9 @@
WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
WebRequestProxyingURLLoaderFactory* factory,
- uint64_t request_id,
- int32_t network_service_request_id,
- int32_t client_request_id,
+ uint64_t profile_request_id,
+ int32_t request_id_for_network_service,
+ int32_t request_id_from_client,
int32_t view_routing_id,
int32_t frame_routing_id,
uint32_t options,
@@ -156,9 +156,9 @@
: factory_(factory),
request_(request),
original_initiator_(request.request_initiator),
- request_id_(request_id),
- network_service_request_id_(network_service_request_id),
- client_request_id_(client_request_id),
+ profile_request_id_(profile_request_id),
+ request_id_for_network_service_(request_id_for_network_service),
+ request_id_from_client_(request_id_from_client),
view_routing_id_(view_routing_id),
frame_routing_id_(frame_routing_id),
options_(options),
@@ -170,19 +170,20 @@
target_client_(std::move(client)),
current_response_(network::mojom::URLResponseHead::New()),
has_any_extra_headers_listeners_(
- network_service_request_id_ != 0 &&
+ request_id_for_network_service_ != 0 &&
WebRequestEventRouter::Get(factory_->browser_context_)
->HasAnyExtraHeadersListener(factory_->browser_context_)),
has_any_security_info_listeners_(
WebRequestEventRouter::Get(factory_->browser_context_)
->HasAnySecurityInfoListener(factory_->browser_context_)),
navigation_response_task_runner_(navigation_response_task_runner) {
- TRACE_EVENT("extensions",
- "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
- "InProgressRequest",
- perfetto::Flow::ProcessScoped(
- request_id_, kWebRequestProxyingURLLoaderFactoryScope),
- "url", request.url.spec());
+ TRACE_EVENT(
+ "extensions",
+ "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
+ "InProgressRequest",
+ perfetto::Flow::ProcessScoped(profile_request_id_,
+ kWebRequestProxyingURLLoaderFactoryScope),
+ "url", request.url.spec());
// If there is a client error, clean up the request.
target_client_.set_disconnect_handler(
@@ -197,13 +198,13 @@
WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
WebRequestProxyingURLLoaderFactory* factory,
- uint64_t request_id,
+ uint64_t profile_request_id,
int32_t frame_routing_id,
const network::ResourceRequest& request)
: factory_(factory),
request_(request),
original_initiator_(request.request_initiator),
- request_id_(request_id),
+ profile_request_id_(profile_request_id),
frame_routing_id_(frame_routing_id),
ukm_source_id_(ukm::kInvalidSourceIdObj),
proxied_loader_receiver_(this),
@@ -214,23 +215,25 @@
has_any_security_info_listeners_(
WebRequestEventRouter::Get(factory_->browser_context_)
->HasAnySecurityInfoListener(factory_->browser_context_)) {
- TRACE_EVENT("extensions",
- "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
- "InProgressRequest",
- perfetto::Flow::ProcessScoped(
- request_id_, kWebRequestProxyingURLLoaderFactoryScope),
- "url", request.url.spec());
+ TRACE_EVENT(
+ "extensions",
+ "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
+ "InProgressRequest",
+ perfetto::Flow::ProcessScoped(profile_request_id_,
+ kWebRequestProxyingURLLoaderFactoryScope),
+ "url", request.url.spec());
}
WebRequestProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {
DCHECK_NE(state_, State::kInvalid);
- TRACE_EVENT("extensions",
- "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
- "~InProgressRequest",
- perfetto::TerminatingFlow::ProcessScoped(
- request_id_, kWebRequestProxyingURLLoaderFactoryScope),
- "state", state_);
+ TRACE_EVENT(
+ "extensions",
+ "WebRequestProxyingURLLoaderFactory::InProgressRequest::"
+ "~InProgressRequest",
Original Bug Report
Cross-origin secret leak via request ID collisions in WebRequest API
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic vulnerability in WebRequestProxyingURLLoaderFactory allows a compromised renderer to use duplicate request IDs to cause map collisions in the browser. This can trick extensions into injecting sensitive cross-origin secrets, such as authentication tokens, into attacker-controlled network requests.
Affected files:
extensions/browser/api/web_request/web_request_proxying_url_loader_factory.ccextensions/browser/api/web_request/web_request_api.cc
Estimated timestamp from git blame: 2018-06-13
Summary
A logic vulnerability in WebRequestProxyingURLLoaderFactory allows a compromised renderer process to perform a confused-deputy attack by providing duplicate request_id values for network requests. Because the browser process lacks uniqueness validation for these IDs in release builds, it can be tricked into routing network events and extension callbacks intended for a victim origin to a request destined for an attacker-controlled origin.
Root Cause Analysis
In extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc, the CreateLoaderAndStart method accepts a request_id parameter directly from the renderer via Mojo. This ID is used as a key in the network_request_id_to_web_request_id_ map. The code uses emplace(), which fails silently if the key already exists:
// extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc:1578
network_request_id_to_web_request_id_.emplace(request_id, web_request_id);
Similarly, in extensions/browser/api/web_request/web_request_api.cc, WebRequestAPI::ProxySet::AssociateProxyWithRequestId uses the ID as a map key, guarded only by a DCHECK which is absent in release builds:
// extensions/browser/api/web_request/web_request_api.cc:289
auto result = request_id_to_proxy_map_.emplace(id, proxy);
DCHECK(result.second) << "Unexpected request ID collision.";
When a collision occurs, the browser continues to use the stale mapping associated with the first request. When the network service subsequently triggers callbacks (like OnLoaderCreated or OnAuthRequired) using that request_id, the factory retrieves the metadata for the first request (e.g., a victim origin) but applies the action to the second request (e.g., the attacker’s origin).
Potential Attack Scenario
- Setup: An attacker compromises a renderer process.
- Collision: The renderer initiates two requests using the same
request_id = X. Request A is forhttps://victim.corp/and Request B is forhttps://attacker.com/. - Hijacking: The browser maps
Xto Request A’s internal state. When the network service creates the loader for Request B, the browser incorrectly routes theTrustedHeaderClientreceiver to Request A’sInProgressRequestobject. - Confused Deputy: Request B’s load triggers an
OnBeforeSendHeadersevent. Request A handles this event, sees its own metadata (victim.corp), and notifies extensions. An extension injects a secret (e.g., a Bearer token) into the headers. - Exfiltration: Request A returns the modified headers (now containing the victim’s token) to the network stack to be used for Request B. The secret is sent to the attacker’s server.
Impact
This issue potentially allows a compromised renderer to bypass Site Isolation guarantees by exfiltrating sensitive credentials, cookies, or authentication headers managed by extensions. It affects any extension that relies on the webRequest API to modify headers or provide credentials based on URL filters.
Suggested Fix
The browser process should validate the uniqueness of the renderer-provided request_id. If emplace or insert fails because the ID is already present in the map, the browser should treat this as a compromised renderer and terminate the process using mojo::ReportBadMessage or a similar mechanism.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.