Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Prefetch
DescriptionInsufficient policy enforcement in Prefetch
ComponentPrefetch
Bug ClassLogic Error
Tracker497491557
Fix commit7ac3a935b453 (chromium/src) +15/-12
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
modified
PrefetchContainer
content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
modified
PrefetchService
content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
modified
ServiceWorkerMainResourceHandle
content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
modified

Files Changed

  • content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
  • content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
From 7ac3a935b453ceb4835c41eb0c65202b07be68f0 Mon Sep 17 00:00:00 2001
From: Hiroshige Hayashizaki <[email protected]>
Date: Tue, 04 Aug 2026 22:03:19 -0700
Subject: [PATCH] Move PrefetchService check a bit

Praparation for https://crrev.com/c/7952998.

This shouldn't impact the behavior much, as the `PrefetchService` check
failure is rare, and it's probably correct to always reject serving
when `PrefetchService` is gone.

Bug: 532959369, 497491557
Change-Id: I23288ff4401feccfeefa9a1af8f61a0ba03e3820
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7950630
Reviewed-by: Taiyo Mizuhashi <[email protected]>
Commit-Queue: Hiroshige Hayashizaki <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1673897}
---

diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
index e2323cd..7e03c954 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -130,6 +130,15 @@
     return;
   }
 
+  PrefetchService* prefetch_service =
+      PrefetchService::GetFromFrameTreeNodeId(frame_tree_node_id_);
+  if (!prefetch_service) {
+    redirect_serving_handle_ = PrefetchServingHandle();
+    TRACE_EVENT_END("loading");
+    std::move(loader_callback_).Run(std::nullopt);
+    return;
+  }
+
   if (redirect_serving_handle_ &&
       redirect_serving_handle_.DoesCurrentURLToServeMatch(
           tentative_resource_request.url)) {
@@ -184,7 +193,7 @@
 
   TRACE_EVENT_END("loading");
   GetPrefetch(
-      tentative_resource_request.url,
+      *prefetch_service, tentative_resource_request.url,
       base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
                      weak_factory_.GetWeakPtr(), tentative_resource_request.url,
 
@@ -194,6 +203,7 @@
 }
 
 void PrefetchURLLoaderInterceptor::GetPrefetch(
+    PrefetchService& prefetch_service,
     const GURL& url,
     base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback)
     const {
@@ -201,21 +211,12 @@
                     perfetto::Flow::FromPointer(
                         const_cast<PrefetchURLLoaderInterceptor*>(this)));
 
-  PrefetchService* prefetch_service =
-      PrefetchService::GetFromFrameTreeNodeId(frame_tree_node_id_);
-  if (!prefetch_service) {
-    TRACE_EVENT_END("loading");
-    std::move(get_prefetch_callback).Run({});
-    return;
-  }
-
-
   auto callback = base::BindOnce(&OnGotPrefetchToServe, frame_tree_node_id_,
                                  url, std::move(get_prefetch_callback));
   auto key = PrefetchKey(initiator_document_token_, url);
   TRACE_EVENT_END("loading");
   PrefetchMatchResolver::FindPrefetch(
-      frame_tree_node_id_, *prefetch_service, std::move(key),
+      frame_tree_node_id_, prefetch_service, std::move(key),
       expected_service_worker_state_, std::move(callback),
       perfetto::Flow::FromPointer(
           const_cast<PrefetchURLLoaderInterceptor*>(this)));
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
index 54e3457b..4d1409e 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
@@ -18,6 +18,7 @@
 namespace content {
 
 class PrefetchContainer;
+class PrefetchService;
 class ServiceWorkerMainResourceHandle;
 
 using PrefetchCompleteCallbackForTesting =
@@ -60,7 +61,8 @@
   // from `PrefetchService` and then goes through other checks in
   // `PrefetchUrlLoaderHelper`.
   // The |get_prefetch_callback| is called with this associated prefetch.
-  void GetPrefetch(const GURL& url,
+  void GetPrefetch(PrefetchService& prefetch_service,
+                   const GURL& url,
                    base::OnceCallback<void(PrefetchServingHandle)>
                        get_prefetch_callback) const;
 
Loading diff…

Original Bug Report

reported by [email protected]

StoragePartition isolation bypass via Speculation Rules Prefetch ServiceWorker inheritance

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A logic flaw in Chrome’s prefetching system allows a navigation in a non-default StoragePartition (e.g., a guest <webview>) to inherit a ServiceWorker controller from the default StoragePartition. This occurs because the prefetch path hardcodes the default StoragePartition for ServiceWorker checks and network fetching, and the subsequent matching logic fails to verify the StoragePartition identity before inheriting the controller. This allows an attacker in an isolated context to read cross-origin data intended for the user’s primary profile.

Affected files:

  • content/browser/service_worker/service_worker_main_resource_handle.cc
  • content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
  • content/browser/preloading/prefetch/prefetch_service.cc
  • content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
  • content/browser/service_worker/service_worker_client.cc
  • content/browser/preloading/prefetch/prefetch_key.h

Estimated timestamp from git blame: 2026-02-13

Description

There is a potential StoragePartition isolation bypass in Chrome’s prefetching logic when handling Service Workers. PrefetchService manages prefetches at the BrowserContext level. However, when evaluating if a ServiceWorker controls a prefetch URL, it explicitly fetches the ServiceWorkerContext and NetworkContext from the default StoragePartition, regardless of the initiator’s partition (e.g., a guest <webview> or Controlled Frame).

See content/browser/preloading/prefetch/prefetch_service.cc lines 850-853:

  ServiceWorkerContextWrapper* service_worker_context =
      g_service_worker_context_for_testing
          ? g_service_worker_context_for_testing
          : browser_context_->GetDefaultStoragePartition()
                ->GetServiceWorkerContext();

Later, when the guest context navigates to the prefetched URL, PrefetchURLLoaderInterceptor attempts to find a matching prefetch. The matching logic relies on PrefetchKey, which only encapsulates the target URL and the initiator’s DocumentToken, completely lacking any field representing the StoragePartition.

Upon a successful match, ServiceWorkerMainResourceHandle::InitializeForRequest is called to wire the guest navigation to the prefetch’s ServiceWorker. This method validates the prefetch client by comparing its top_frame_origin and blink::StorageKey against the navigation context. However, neither url::Origin nor blink::StorageKey encodes StoragePartition identity. Consequently, the check passes, and the navigation incorrectly inherits the default partition’s ServiceWorkerRegistration.

Potential Steps to Reproduce

Note: These are suggested steps; our tooling agent cannot execute code to provide a working PoC.

  1. A user visits https://victim.test in a standard browser tab, registering a Service Worker in the default StoragePartition.
  2. The user runs a malicious Chrome App or Isolated Web App containing a guest context (e.g., <webview>), which operates in a separate, isolated StoragePartition.
  3. From within the attacker-controlled guest context, a Speculation Rules prefetch (<script type="speculationrules">) is triggered for https://victim.test.
  4. The prefetch executes using the default StoragePartition’s network context and ServiceWorker context.
  5. The attacker then navigates the guest context to https://victim.test.
  6. The prefetch interceptor incorrectly matches the prefetch and adopts the default-partition ServiceWorker as the controller for the guest navigation.
  7. Subsequent subresource fetches from the guest document at https://victim.test are routed through the default partition’s ServiceWorker, allowing the attacker to read authenticated or cached cross-origin data.

Impact

This vulnerability allows an attacker to completely bypass StoragePartition isolation boundaries. By controlling a guest context, the attacker can force it to be controlled by a Service Worker from the user’s primary, default profile. This enables the extraction of sensitive, authenticated data from arbitrary origins that the user has visited, leading to a severe cross-partition data leak.

Suggested Fix

  1. Enforce Partition Matching: PrefetchKey should be updated to include the StoragePartition (or an identifier for it) so that prefetches initiated in an isolated partition cannot match navigations in the default partition, and vice versa.
  2. Use Correct Contexts: PrefetchService and PrefetchStreamingURLLoader must use the ServiceWorkerContext and NetworkContext associated with the initiator’s StoragePartition, rather than unconditionally calling BrowserContext::GetDefaultStoragePartition().
  3. Strict Inheritance Checks: ServiceWorkerMainResourceHandle::InitializeForRequest should explicitly verify that the StoragePartition of the client_for_prefetch matches the StoragePartition of the ongoing navigation before calling SetControllerRegistration.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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.

View on issue tracker