Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in ServiceWorker
DescriptionInsufficient policy enforcement in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Tracker517655543
Fix commitbaf7a1818eee (chromium/src) +23/-13
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • content/browser/service_worker/service_worker_main_resource_loader.cc
From baf7a1818eee16b05695f67c7dcb850df86f2435 Mon Sep 17 00:00:00 2001
From: Yoshisato Yanagisawa <[email protected]>
Date: Sun, 14 Jun 2026 20:43:33 -0700
Subject: [PATCH] Bugfix: This CL fixes a bug reported in crbug.com/517655543

[analysis & reasoning]
https://docs.google.com/document/d/1IBGo0-8Q3skKIHFGRzobo44J_tXou-BjSUmsMKmM7Mg

Bug: 517655543
Change-Id: If43076756ea397d2894b0e69d9c037fa81b9b3b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7908441
Commit-Queue: Yoshisato Yanagisawa <[email protected]>
Reviewed-by: Shunya Shishido <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1646570}
---

diff --git a/content/browser/service_worker/service_worker_main_resource_loader.cc b/content/browser/service_worker/service_worker_main_resource_loader.cc
index 909b5be..87250f8 100644
--- a/content/browser/service_worker/service_worker_main_resource_loader.cc
+++ b/content/browser/service_worker/service_worker_main_resource_loader.cc
@@ -927,22 +927,32 @@
         cache_matcher_->cache_lookup_duration();
 
     // Block invalid responses from the static router.
+    network::CrossOriginEmbedderPolicy cross_origin_embedder_policy;
+    network::mojom::CrossOriginEmbedderPolicyReporter*
+        cross_origin_embedder_policy_reporter = nullptr;
+    network::DocumentIsolationPolicy document_isolation_policy;
+    network::mojom::DocumentIsolationPolicyReporter*
+        document_isolation_policy_reporter = nullptr;
     if (service_worker_client_ && service_worker_client_->container_host()) {
       ServiceWorkerContainerHostForClient* container_host =
           service_worker_client_->container_host();
-      if (!IsValidStaticRouterResponse(
-              resource_request_, response,
-              container_host->policy_container_policies()
-                  .cross_origin_embedder_policy,
-              container_host->cross_origin_embedder_policy_reporter().get(),
-              container_host->policy_container_policies()
-                  .document_isolation_policy,
-              container_host->document_isolation_policy_reporter().get()) &&
-          base::FeatureList::IsEnabled(
-              features::kServiceWorkerStaticRouterOpaqueCheck)) {
-        CommitCompleted(net::ERR_FAILED, "Invalid response from static router");
-        return;
-      }
+      cross_origin_embedder_policy = container_host->policy_container_policies()
+                                         .cross_origin_embedder_policy;
+      cross_origin_embedder_policy_reporter =
+          container_host->cross_origin_embedder_policy_reporter().get();
+      document_isolation_policy =
+          container_host->policy_container_policies().document_isolation_policy;
+      document_isolation_policy_reporter =
+          container_host->document_isolation_policy_reporter().get();
+    }
+    if (!IsValidStaticRouterResponse(
+            resource_request_, response, cross_origin_embedder_policy,
+            cross_origin_embedder_policy_reporter, document_isolation_policy,
+            document_isolation_policy_reporter) &&
+        base::FeatureList::IsEnabled(
+            features::kServiceWorkerStaticRouterOpaqueCheck)) {
+      CommitCompleted(net::ERR_FAILED, "Invalid response from static router");
+      return;
     }
   }
 
Loading diff…

Original Bug Report

reported by [email protected]

Static Router validation bypass allows cross-origin opaque response as navigation

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 flaw in ServiceWorkerMainResourceLoader::DidDispatchFetchEvent potentially allows a matched Static-Router cache source response to bypass validation checks for navigation requests. Because the client’s container host is structurally uninitialized at this point in the lifecycle, the security check is skipped. This potentially allows an attacker to load and read cross-origin opaque responses in a same-origin navigation context, bypassing the Same-Origin Policy (SOP).

Affected files:

  • content/browser/service_worker/service_worker_main_resource_loader.cc

Estimated timestamp from git blame: 2026-04-12

Root Cause Analysis

In content/browser/service_worker/service_worker_main_resource_loader.cc, within the method ServiceWorkerMainResourceLoader::DidDispatchFetchEvent, the browser attempts to validate responses matched via the Static-Router cache source (e.g., kCache or kRaceNetworkAndCache source types). This verification ensures that matched responses are valid (e.g., preventing opaque cross-origin responses from being loaded as a main navigation document) and conform to Cross-Origin Resource Policy (CORP) constraints:

// content/browser/service_worker/service_worker_main_resource_loader.cc
if (IsMatchedRouterSourceType(
        network::mojom::ServiceWorkerRouterSourceType::kCache) || ...) {
  ...
  // Block invalid responses from the static router.
  if (service_worker_client_ && service_worker_client_->container_host()) {
    ServiceWorkerContainerHostForClient* container_host =
        service_worker_client_->container_host();
    if (!IsValidStaticRouterResponse(
            resource_request_, response, ...) &&
        base::FeatureList::IsEnabled(
            features::kServiceWorkerStaticRouterOpaqueCheck)) {
      CommitCompleted(net::ERR_FAILED, "Invalid response from static router");
      return;
    }
  }
}

However, service_worker_client_->container_host() is structurally nullptr at this point in the lifecycle for all main resource navigations. The container_host_ is assigned exactly once within ServiceWorkerClient::CommitResponse() (content/browser/service_worker/service_worker_client.cc), which is invoked during the navigation commit phase strictly after the response has been received, processed by DidDispatchFetchEvent, and delivered to the navigation loader.

Because of this lifecycle mismatch, this safety check is bypassed and behaves as dead code for window navigations. Even though kServiceWorkerStaticRouterOpaqueCheck is enabled by default, the validation block is never entered.

Potential Security Implications

Because the browser bypasses this security check, an attacker can potentially bypass the Same-Origin Policy (SOP) and Site Isolation by serving a cached cross-origin opaque response body within a same-origin navigation context.

While Opaque Response Blocking (ORB) restricts certain response types like HTML and JSON from being cached as opaque, other sensitive cross-origin resources (such as JavaScript configuration files, CSS, images, and media) can be fully exfiltrated.

Suggested / Potential Attacker Steps

Note: These are potential steps as our tooling agent does not have the capability to execute code or run a live proof of concept.

  1. A user visits https://attacker.example, which registers a Service Worker with a static routing rule matching a specific path (e.g., /leak) with the source set to cache:
    self.addEventListener('install', e => {
      e.addRoutes([{condition:{urlPattern:{pathname:'/leak'}}, source:'cache'}]);
    });
    
  2. The Service Worker fetches a sensitive cross-origin resource with {mode: 'no-cors'} and puts the resulting opaque response into Cache Storage under /leak:
    const r = await fetch('https://victim.example/secret_config.js', {mode:'no-cors', credentials:'include'});
    const c = await caches.open('x');
    await c.put(new Request('/leak'), r);
    
  3. The attacker’s page embeds an iframe pointing to https://attacker.example/leak.
  4. The browser process intercepts the navigation request, matches the static routing rule, retrieves the opaque response from Cache Storage, and bypasses the validation gate because container_host() is nullptr.
  5. The document is committed at attacker.example/leak containing the victim’s cross-origin response body.
  6. The parent page reads the iframe’s document via standard DOM/JS (iframe.contentDocument.documentElement.outerHTML), successfully exfiltrating the cross-origin data.

Suggested Fix

To remediate this issue, the browser should not rely on a fully committed container_host() to perform these checks during DidDispatchFetchEvent. Instead, the loader should fetch the client’s security policies (such as COEP and DIP) directly from the ServiceWorkerClient (which is available before commit) or perform base validation of the response type (e.g., ensuring IsValidServiceWorkerResponse is checked on the response and resource_request_ parameters) without requiring the container host to be fully initialized.

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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.

View on issue tracker