CVE-2026-17824
Overview
Files Changed
content/browser/service_worker/service_worker_main_resource_loader.cc
Patch
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; } }
Original Bug Report
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.
- 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 tocache:self.addEventListener('install', e => { e.addRoutes([{condition:{urlPattern:{pathname:'/leak'}}, source:'cache'}]); }); - 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); - The attacker’s page embeds an iframe pointing to
https://attacker.example/leak. - 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()isnullptr. - The document is committed at
attacker.example/leakcontaining the victim’s cross-origin response body. - 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.