Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactCross-site request forgery in DevTools
DescriptionCross-site request forgery in DevTools
ComponentDevTools
Sub-componentPlatform>DevTools
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker511791538
Fix commit6eb54c22b20c (chromium/src) +47/-3
CWECWE-352 (CSRF)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-10 (per vendor advisory)
Fix landed2026-08-28 (public git)
Disclosed2026-09-22
Patch gap25 days — fix public in source before the advisory

Background

DevTools network resource loading
the mechanism by which the DevTools front-end asks the browser process to fetch resources like source maps on its behalf via DevToolsUIBindings::LoadNetworkResource.
`site_for_cookies`
a field on network::ResourceRequest that identifies the top-level site for a request and governs whether same-site (non-SameSite=None) cookies are attached.
`request_initiator`
a field on network::ResourceRequest naming the origin that initiated the request, used by the network service for CORS and other origin-based security decisions.
Cross-site request forgery (CSRF)
an attack in which a request carries a victim’s ambient cookies to a target site, causing state-changing actions the victim did not intend.

Root Cause Analysis

When DevTools fetched a network resource in DevToolsUIBindings::LoadNetworkResource, it built a network::ResourceRequest whose site_for_cookies was derived directly from the resource URL itself via net::SiteForCookies::FromUrl(gurl), and it left request_initiator unset. Deriving site_for_cookies from the resource URL makes every such fetch appear first-party to its own target, so the network service attaches the victim’s same-site cookies to an arbitrary cross-site URL that a page could cause DevTools to load. This violated the invariant that a subresource fetch must carry the security context (initiator and first-party site) of the page that actually caused it — the inspected page — not the context of the URL being fetched.

The fix reads the inspected page’s primary main frame origin via GetPrimaryMainFrame()->GetLastCommittedOrigin(), sets request_initiator to that origin, and computes site_for_cookies with net::SiteForCookies::FromOrigin(inspected_origin), so the request is treated as originating from the inspected page and only carries cookies appropriate to that site.

The added test confirms site_for_cookies.IsFirstParty(resource_url) is now false while it is equivalent to the inspected origin’s site.

Key insight
The single mistake was treating the fetched resource’s own URL as its first-party context, which forged a first-party relationship for any cross-site URL and let DevTools-initiated fetches carry ambient cookies; the fix binds the request’s initiator and site_for_cookies to the inspected page’s committed origin instead.

Attack Path

  1. Set up a malicious inspected page An attacker gets a victim to open DevTools on a page (or content) under attacker influence that can trigger a DevTools network resource load, such as via a crafted source map URL.
  2. Trigger a cross-site resource fetch The page causes DevToolsUIBindings::LoadNetworkResource to fetch an arbitrary cross-site target URL, which builds a network::ResourceRequest for that URL.
  3. First-party context is forged Because site_for_cookies was set from the target URL via net::SiteForCookies::FromUrl(gurl) and request_initiator was unset, the network service treats the request as first-party to the target and attaches the victim’s same-site cookies.
  4. Cross-site state change or data exposure The forged, cookie-bearing request reaches the target site as an authenticated CSRF request, potentially performing a state-changing action or exposing authenticated response data. (inferred)

Impact Assessment

An attacker can cause DevTools-initiated resource fetches to carry the victim’s ambient same-site cookies to arbitrary cross-site URLs, enabling cross-site request forgery in the browser-process-driven DevTools fetch path with the precondition that the victim has DevTools engaged on attacker-influenced content. The advisory classifies this as a DevTools CSRF logic error and states no more; any escalation to authenticated data exfiltration or further state change beyond cookie-bearing request forgery is (inferred). The advisory does not report exploitation in the wild.

Changed Functions

FunctionChangeNotes
if
chrome/browser/devtools/devtools_ui_bindings.cc
modified
TEST_F
chrome/browser/devtools/devtools_ui_bindings_unittest.cc
modified

Files Changed

  • chrome/browser/devtools/devtools_ui_bindings_unittest.cc
  • chrome/browser/devtools/devtools_ui_bindings.cc

Audit Directions

  • Fetch-URL-derived first-party context
    Look for other browser-process request builders that set site_for_cookies from the target URL via net::SiteForCookies::FromUrl instead of an inspected/initiating page origin.
  • Unset request_initiator on privileged fetches
    Audit WebUI and DevTools-driven network::ResourceRequest construction paths that omit request_initiator, since a missing initiator disables origin-based network-service protections.
  • Ambient-authority proxied requests
    Review features where a privileged UI fetches URLs on behalf of a less-trusted page and confirm the resulting request inherits the page’s security context rather than the fetched resource’s.
From 6eb54c22b20c417dee1409564e8f2d635ff131a9 Mon Sep 17 00:00:00 2001
From: Alex Rudenko <alexrudenko@chromium.org>
Date: Fri, 28 Aug 2026 05:56:45 -0700
Subject: [PATCH] Use inspected page origin for DevTools network resource requests.

When DevTools loads network resources (such as source maps), it now sets
the request initiator and site-for-cookies based on the origin of the
inspected page's primary main frame. Previously, site-for-cookies was
derived from the resource URL itself, and the request initiator was not
set. This change ensures proper cookie and security policy handling for
these requests.

Fixed: 511791538
Change-Id: I9d4f069f5022c62b50d755afe126d5d6fd5131c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8310909
Auto-Submit: Alex Rudenko <alexrudenko@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1687931}
---
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index ccf1664d..c5c57ba 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1306,12 +1306,16 @@
 
   network::ResourceRequest resource_request;
   resource_request.url = gurl;
-  // TODO(caseq): this preserves behavior of URLFetcher-based implementation.
-  // We really need to pass proper first party origin from the front-end.
-  resource_request.site_for_cookies = net::SiteForCookies::FromUrl(gurl);
   resource_request.headers.AddHeadersFromString(headers);
 
   content::WebContents* target_tab = delegate_->GetInspectedWebContents();
+  if (target_tab) {
+    const url::Origin& inspected_origin =
+        target_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin();
+    resource_request.request_initiator = inspected_origin;
+    resource_request.site_for_cookies =
+        net::SiteForCookies::FromOrigin(inspected_origin);
+  }
 
   NetworkResourceLoader::URLLoaderFactoryHolder url_loader_factory;
   if (gurl.SchemeIsFile()) {
diff --git a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
index 7885666a..716d446 100644
--- a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
@@ -31,10 +31,12 @@
 #include "content/public/test/url_loader_interceptor.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/base/net_errors.h"
+#include "net/cookies/site_for_cookies.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
 
 using testing::_;
 
@@ -234,6 +236,44 @@
   }
 }
 
+TEST_F(DevToolsUIBindingsLoadNetworkResourceTest,
+       UsesInspectedPageAsRequestInitiator) {
+  const GURL inspected_url("http://a.test/page.html");
+  const GURL resource_url("http://b.test/source.map");
+
+  content::WebContents* inspected_web_contents =
+      web_contents_factory_.CreateWebContents(profile_.get());
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      inspected_web_contents, inspected_url);
+  auto delegate =
+      std::make_unique<MockDevToolsUIBindingsDelegate>(inspected_web_contents);
+  bindings()->SetDelegate(delegate.release());
+
+  std::optional<network::ResourceRequest> captured_request;
+  content::URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
+      [&](content::URLLoaderInterceptor::RequestParams* params) {
+        captured_request = params->url_request;
+        content::URLLoaderInterceptor::WriteResponse(
+            "HTTP/1.1 200 OK\n\n", "{}", params->client.get());
+        return true;
+      }));
+
+  base::RunLoop run_loop;
+  CallLoadNetworkResource(
+      resource_url.spec(), "", 0,
+      base::BindLambdaForTesting(
+          [&](const base::Value*) { run_loop.Quit(); }));
+  run_loop.Run();
+
+  ASSERT_TRUE(captured_request.has_value());
+  EXPECT_EQ(captured_request->url, resource_url);
+  const url::Origin inspected_origin = url::Origin::Create(inspected_url);
+  EXPECT_EQ(captured_request->request_initiator, inspected_origin);
+  EXPECT_TRUE(captured_request->site_for_cookies.IsEquivalent(
+      net::SiteForCookies::FromOrigin(inspected_origin)));
+  EXPECT_FALSE(captured_request->site_for_cookies.IsFirstParty(resource_url));
+}
+
 TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) {
   std::vector<std::pair<std::string, std::string>> tests = {
       {"random-string", "devtools://devtools/"},
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
index 7885666a..716d446 100644
--- a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
@@ -31,10 +31,12 @@
 #include "content/public/test/url_loader_interceptor.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/base/net_errors.h"
+#include "net/cookies/site_for_cookies.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
 
 using testing::_;
 
@@ -234,6 +236,44 @@
   }
 }
 
+TEST_F(DevToolsUIBindingsLoadNetworkResourceTest,
+       UsesInspectedPageAsRequestInitiator) {
+  const GURL inspected_url("http://a.test/page.html");
+  const GURL resource_url("http://b.test/source.map");
+
+  content::WebContents* inspected_web_contents =
+      web_contents_factory_.CreateWebContents(profile_.get());
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      inspected_web_contents, inspected_url);
+  auto delegate =
+      std::make_unique<MockDevToolsUIBindingsDelegate>(inspected_web_contents);
+  bindings()->SetDelegate(delegate.release());
+
+  std::optional<network::ResourceRequest> captured_request;
+  content::URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
+      [&](content::URLLoaderInterceptor::RequestParams* params) {
+        captured_request = params->url_request;
+        content::URLLoaderInterceptor::WriteResponse(
+            "HTTP/1.1 200 OK\n\n", "{}", params->client.get());
+        return true;
+      }));
+
+  base::RunLoop run_loop;
+  CallLoadNetworkResource(
+      resource_url.spec(), "", 0,
+      base::BindLambdaForTesting(
+          [&](const base::Value*) { run_loop.Quit(); }));
+  run_loop.Run();
+
+  ASSERT_TRUE(captured_request.has_value());
+  EXPECT_EQ(captured_request->url, resource_url);
+  const url::Origin inspected_origin = url::Origin::Create(inspected_url);
+  EXPECT_EQ(captured_request->request_initiator, inspected_origin);
+  EXPECT_TRUE(captured_request->site_for_cookies.IsEquivalent(
+      net::SiteForCookies::FromOrigin(inspected_origin)));
+  EXPECT_FALSE(captured_request->site_for_cookies.IsFirstParty(resource_url));
+}
+
 TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) {
   std::vector<std::pair<std::string, std::string>> tests = {
       {"random-string", "devtools://devtools/"},
Loading diff…

Original Bug Report

reported by vm...@google.com

SameSite=Strict CSRF and Fetch Metadata bypass in DevTools LoadNetworkResource

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: When DevTools falls back to host bindings to load network resources (like source maps for dedicated workers), it uses DevToolsUIBindings::LoadNetworkResource. This function issues the request using the browser process URL loader factory with an empty request_initiator and a site_for_cookies matching the target URL, causing the network stack to treat it as a trusted, browser-initiated request. This allows an attacker to perform credentialed GET-based CSRF attacks that bypass SameSite=Strict and Fetch Metadata protections.

Affected files:

  • chrome/browser/devtools/devtools_ui_bindings.cc

Estimated timestamp from git blame: 2019-12-15

Summary

A potential vulnerability in DevToolsUIBindings::LoadNetworkResource allows an attacker to trigger credentialed GET requests that bypass SameSite=Strict cookie restrictions and Fetch Metadata Resource Isolation Policies (Sec-Fetch-Site).

The issue arises because the browser process issues these requests with an empty request_initiator and a site_for_cookies matching the target URL, causing the network stack to treat them as browser-initiated (similar to a user typing a URL directly into the address bar). This behavior can be reliably triggered via source-map loading in dedicated workers.

Technical Details

The function DevToolsUIBindings::LoadNetworkResource in chrome/browser/devtools/devtools_ui_bindings.cc handles network resource loading for the DevTools frontend in cases where target-specific loading via CDP fails or is unavailable. When processing a request, it initializes a network::ResourceRequest with the following configuration:

  1. Site For Cookies: resource_request.site_for_cookies = net::SiteForCookies::FromUrl(gurl);. This designates the request as first-party for the target URL.
  2. Initiator: The request_initiator field is left uninitialized, defaulting to std::nullopt.
  3. Credentials: The credentials_mode defaults to mojom::CredentialsMode::kInclude.
  4. Dispatch: The request is dispatched through StoragePartitionImpl::GetURLLoaderFactoryForBrowserProcess().

In the Chromium network stack (//services/network/sec_header_helpers.cc), a request from the browser process with no initiator is assigned a SAME_SITE_STRICT cookie context and a Sec-Fetch-Site: none header. This allows the request to carry SameSite=Strict cookies and bypass security policies that trust the Sec-Fetch-Site: none value, which is intended only for browser-UI-initiated navigation or actions.

Reachability via Dedicated Workers

While DevTools typically loads resources via the inspected target’s network factory (which enforces isolation and CSP), dedicated workers currently fall back to this privileged browser-side binding:

  1. When the DevTools frontend attempts to load a source map for a dedicated worker via the standard Network.loadNetworkResource CDP command, it fails with a “Target not supported” error. This is because DedicatedWorkerDevToolsAgentHost does not provide the required network factory parameters, resulting in an invalid factory being generated in the browser process.
  2. The DevTools frontend (PageResourceLoader.ts) catches this protocol error. Because it is not a CSP violation, it ignores the error and falls back to loadFromHostBindings.
  3. loadFromHostBindings uses the associated Mojo interface blink::mojom::DevToolsFrontendHost to invoke the vulnerable DevToolsUIBindings::LoadNetworkResource browser-process logic.

This path also entirely bypasses the Content Security Policy (CSP) checks that are normally applied to frame-based resource loads in DevTools.

Suggested Exploitation Steps

(Note: These are potential steps based on code analysis; we do not have a working Proof of Concept yet).

  1. An attacker identifies a target GET action on https://victim.example/action protected by SameSite=Strict cookies or Sec-Fetch-Site restrictions.
  2. The attacker serves a page from https://attacker.example that spawns a dedicated worker: new Worker('w.js');.
  3. The worker script w.js contains a source map declaration pointing to the victim endpoint: //# sourceMappingURL=https://victim.example/action.
  4. A victim, logged into https://victim.example, visits https://attacker.example.
  5. The victim opens DevTools (e.g., F12).
  6. The DevTools frontend automatically attempts to fetch the source map for w.js.
  7. The network request to https://victim.example/action is issued by the browser process. It carries SameSite=Strict cookies and the Sec-Fetch-Site: none header, successfully bypassing cross-site protections and triggering the CSRF.

Suggested Fix

DevToolsUIBindings::LoadNetworkResource should not construct ResourceRequest objects that mimic browser-initiated navigation.

  1. Provide an Initiator: Set request_initiator to the origin of the page being inspected, or a specific DevTools origin, rather than leaving it as std::nullopt.
  2. Correct SiteForCookies: Do not blindly use net::SiteForCookies::FromUrl(gurl). The site_for_cookies should reflect the context of the inspected page, not the requested resource URL.
  3. Credentials: Re-evaluate if mojom::CredentialsMode::kInclude is appropriate for host-binding fallbacks, or if it should default to kOmit or kSameOrigin.

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


Results 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