CVE-2026-3940
Overview
Background
- `InspectorResourceContentLoader`
- the Blink-side DevTools helper (
inspector_resource_content_loader.cc) that gathers a page’s document and stylesheet contents for the inspector by issuing its own internal resource requests through the document’sResourceFetcher. - `mojom::FetchCacheMode::kOnlyIfCached`
- a fetch cache mode that only satisfies a request from cache and never goes to the network; per the commit message it is only honoured together with
network::mojom::RequestMode::kSameOrigin. - `network::mojom::RequestMode`
- the CORS mode of a request; the default is
kNoCors, and the patch now setskSameOriginon the DevTools reload request. - Service worker interception
- when a document is controlled by a service worker, subresource and re-fetch requests from that document can be routed to the worker’s
fetchhandler, which may answer from its cache or re-issue the request to the network.
Root Cause Analysis
The vulnerable code is in the request-building path of InspectorResourceContentLoader (the hunk does not show the enclosing function’s name; it is the code that builds a ResourceRequest for document->Url() with kOnlyIfCached and hands it to document->Fetcher()). DevTools intends this re-fetch of the document to be cache-only, so that inspecting a page does not trigger new network traffic. Before the patch, the anonymous-namespace helper IsServiceWorkerPresent() checked whether the document was fetched via, or is controlled by, a service worker, and if so the code explicitly downgraded the request to mojom::FetchCacheMode::kDefault. The removed comment says this was a workaround: only-if-cached combined with the default kNoCors mode made the service worker throw when it repeated the request (crbug.com/823392, crbug.com/1098389). The result was that on any service-worker-controlled page, the DevTools-internal request lost its cache-only restriction and could reach the service worker and the network with ordinary cache semantics.
The fix deletes IsServiceWorkerPresent() and the kDefault downgrade, and always sets network::mojom::RequestMode::kSameOrigin so that kOnlyIfCached is valid and also works through the service worker. DevTools re-fetches therefore stay cache-only in every case.
kOnlyIfCached → kDefault) whenever a service worker was present. The fix keeps kOnlyIfCached unconditionally and pairs it with kSameOrigin, the mode it requires, so the workaround is no longer needed.Attack Path
- Service-worker-controlled page
An attacker-controlled site registers a service worker that controls its document, so
IsServiceWorkerPresent()would return true. - User opens DevTools
The victim opens DevTools on that page, and
InspectorResourceContentLoaderbuilds an internalResourceRequestfor the document URL to collect its content. - Cache-only policy dropped
Before the patch, the service worker check reset the request’s cache mode to
kDefault, so the request was no longer limited to the cache. - Request leaves the cache The DevTools-initiated request could then be handled by the page’s service worker and/or go to the network, which lets the site observe or respond to inspector activity it should never see (inferred; the diff shows only the cache-mode change, not a specific observable effect).
Impact Assessment
Files Changed
third_party/blink/renderer/core/inspector/inspector_resource_content_loader.cc
Audit Directions
- Conditional relaxation of internal-request policyLook for DevTools and other internal (
RequestContextType::INTERNAL) loaders that change cache mode, credentials mode or request mode based on page-controlled state such as service worker presence, because the page can then choose which policy applies. - Incompatible fetch-option combinationsLook for code that pairs
kOnlyIfCached(or similar restrictive modes) with request modes the fetch spec says they need (kSameOrigin), and for workarounds that “fix” the incompatibility by loosening the restriction instead of correcting the mode. - Inspector-triggered traffic visible to the pageCheck other inspector agents that re-fetch page resources (stylesheets, scripts, source maps) for paths where a service worker or the network can see DevTools-originated requests.
Patch
From a6d7acc60f1c45f3fd24671a5283a1b4cb585948 Mon Sep 17 00:00:00 2001 From: Alex Rudenko <[email protected]> Date: Tue, 13 Jan 2026 00:39:51 -0800 Subject: [PATCH] Set resource request mode to kSameOrigin and only use the kOnlyIfCached mode kOnlyIfCached only works if kSameOrigin is set [1]. The default request mode is kNoCors[2]. While kNoCors + kOnlyIfCached seems to work for getting cached resources, this combination does not work when getting cached requests from the service worker. This CL changes the mode to kSameOrigin which should allow the request to hit the service worker cache. The intention of this part of the resource content loader seems to be to avoid hitting the network so the combination of kOnlyIfCached + kSameOrigin would achieve that. [1]: [2]: https: //source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/request.cc;l=649;drc=f3534bf8a057a7dcb2f239e855fc71b2faf6dddf https: //source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/fetch_request_data.h;l=240;drc=4641a043d7914ce31515751c357158d46f4c937a Fixed: 470574526 Change-Id: Ib92a9df516ee94a8bfbea57f5da17b5e21f9261a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7409996 Commit-Queue: Danil Somsikov <[email protected]> Auto-Submit: Alex Rudenko <[email protected]> Reviewed-by: Danil Somsikov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1568281} --- diff --git a/third_party/blink/renderer/core/inspector/inspector_resource_content_loader.cc b/third_party/blink/renderer/core/inspector/inspector_resource_content_loader.cc index 53911c8..7100f9e 100644 --- a/third_party/blink/renderer/core/inspector/inspector_resource_content_loader.cc +++ b/third_party/blink/renderer/core/inspector/inspector_resource_content_loader.cc @@ -32,22 +32,6 @@ return !url.IsValid() || url.IsAboutBlankURL() || url.IsAboutSrcdocURL(); } -bool IsServiceWorkerPresent(Document* document) { - DocumentLoader* loader = document->Loader(); - if (!loader) - return false; - - if (loader->GetResponse().WasFetchedViaServiceWorker()) - return true; - - WebServiceWorkerNetworkProvider* provider = - loader->GetServiceWorkerNetworkProvider(); - if (!provider) - return false; - - return provider->ControllerServiceWorkerID() >= 0; -} - } // namespace // NOTE: While this is a RawResourceClient, it loads both raw and css stylesheet @@ -111,17 +95,11 @@ resource_request = ResourceRequest(document->Url()); resource_request.SetCacheMode(mojom::FetchCacheMode::kOnlyIfCached); } + // kOnlyIfCached requires kSameOrigin mode. + resource_request.SetMode(network::mojom::RequestMode::kSameOrigin); resource_request.SetRequestContext( mojom::blink::RequestContextType::INTERNAL); - if (IsServiceWorkerPresent(document)) { - // If the request is going to be intercepted by a service worker, then - // don't use only-if-cached. only-if-cached will cause the service worker - // to throw an exception if it repeats the request, which is a problem: - // crbug.com/823392 crbug.com/1098389 - resource_request.SetCacheMode(mojom::FetchCacheMode::kDefault); - } - ResourceFetcher* fetcher = document->Fetcher(); const DOMWrapperWorld* world =
Original Bug Report
DevTools Sources panel with Service Worker causes POST to be resent bypassing SameSite=Strict
Steps to reproduce the problem
- Visit https:// jtw.sh/ samesite-post-bypass-ce7139c3/target-sw.php so that cookies are set and a service worker is registered on the target website (
jtw.sh) - Visit the attacker’s site: https:// vps.jorianwoltjer.com/ samesite-post-bypass-ce7139c3/csrf-sw.html
- Right click and choose Inspect to open the DevTools, or press
Ctrl+Shift+IorF12on the keyboard - In the background, a malicious POST request with all cookies has now been sent to the target. See it in the Sources tab viewing the
target-sw.phpfile (see recording samesite-post-bypass-sw.mp4)
Source code for reproducing this locally is also provided in the attached samesite-post-bypass-sw.zip. Run with php -S 0.0.0.0:8000
Problem Description
In DevTools, the Sources panel shows source code of the current page and its associated files. It gets these resources from the cache, or re-fetches them if needed. This new request is initiated from the current site, which is a contradiction if the original navigation came from cross-site. It means that the request for the Sources tab will contain SameSite=Strict cookies even for POST requests.
Weirdly, this re-fetching process seems to only happen if there is a Service Worker registered for the target. Otherwise the error “Content unavailable. Resource was not cached” is shown in place of the content. The service worker doesn’t have to do anything, it just needs to be registered on the target. So any site that uses a service worker and relies on SameSite for Cross-Site Request Forgery (CSRF) protection is vulnerable.
From the attacker a simple top-level form submission is enough. While it shouldn’t work initially, after the DevTools are opened on the resulting page, the request is resent and SameSite=Strict cookies are now sent with the body payload from the attacker.
<form action="..." method="post">
<input type="text" name="name" value="value">
</form>
<script>
document.forms[0].submit();
</script>
This requires some unusual user interaction (F12), but to me still felt unexpected. It should not be possible for an attacker to prepare a request so that it contains their malicious POST body with the user’s SameSite=Strict cookies without it being clearly visible.
The request that the DevTools sources tab sends should inherit the SameSite-ness (initiator?) of the original request so that it is as identical as possible, and doesn’t increase the risk of CSRF.
Summary
DevTools Sources panel with Service Worker causes POST to be resent bypassing SameSite=Strict
Custom Questions
Reporter credit:
Jorian Woltjer, Mian, bug_blitzer
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: No \