Chrome · DevTools
CVE-2026-18014
Logic Error in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forfront_end/models/trace/handlers/NetworkRequestsHandler.test.ts |
modified | |
iffront_end/models/trace/handlers/NetworkRequestsHandler.test.ts |
modified | |
itfront_end/models/trace/handlers/NetworkRequestsHandler.test.ts |
modified |
Files Changed
front_end/models/trace/handlers/NetworkRequestsHandler.test.tsfront_end/models/trace/handlers/NetworkRequestsHandler.ts
Patch
From 23882774076cba004403101233c1a06a25595b49 Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Mon, 15 Jun 2026 05:45:54 -0700
Subject: [PATCH] Implement protocol allowlist and sanitization in NetworkRequestsHandler
Bug: 523248021
Change-Id: I6cd18c77d7b7477a8ac4a2b9f1342db600ffe58e
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7942589
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
Auto-Submit: Danil Somsikov <dsv@chromium.org>
---
diff --git a/front_end/models/trace/handlers/NetworkRequestsHandler.test.ts b/front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
index 2767179..11de299 100644
--- a/front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
+++ b/front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
@@ -460,12 +460,102 @@
await Trace.Handlers.ModelHandlers.NetworkRequests.finalize();
const url = 'https://andydavies.github.io/agent-tests/render-blocking/css/styles.css';
const data = Trace.Handlers.ModelHandlers.NetworkRequests.data();
-
const request = data.byTime.find(e => e.args.data.url === url);
assert.isOk(request);
assert.strictEqual(request.args.data.renderBlocking, 'blocking');
});
+
+ describe('protocol allowlist and sanitization', () => {
+ beforeEach(() => {
+ Trace.Handlers.ModelHandlers.Meta.reset();
+ });
+
+ async function modifyRedirectEvent(
+ testContext: Mocha.Context,
+ eventPicker: (events: Trace.Types.Events.ResourceSendRequest[]) => Trace.Types.Events.ResourceSendRequest,
+ newUrl: string): Promise<Trace.Types.Events.Event[]> {
+ const rawEvents = await TraceLoader.rawEvents(testContext, 'redirects.json.gz');
+ const traceEvents = [...rawEvents].sort((a, b) => a.ts - b.ts);
+
+ const requestsById = new Map<string, Trace.Types.Events.ResourceSendRequest[]>();
+ for (const event of traceEvents) {
+ if (Trace.Types.Events.isResourceSendRequest(event)) {
+ const requestId = event.args.data.requestId;
+ if (!requestsById.has(requestId)) {
+ requestsById.set(requestId, []);
+ }
+ requestsById.get(requestId)!.push(event);
+ }
+ }
+
+ let modified = false;
+ for (const events of requestsById.values()) {
+ if (events.length > 1) {
+ events.sort((a, b) => a.ts - b.ts);
+ const eventToModify = eventPicker(events);
+ const index = traceEvents.indexOf(eventToModify);
+ if (index !== -1) {
+ traceEvents[index] = updateResourceSendRequestData(eventToModify, {url: newUrl});
+ modified = true;
+ break;
+ }
+ }
+ }
+ assert.isTrue(modified, 'Did not find a request with redirects to modify');
+ return traceEvents;
+ }
+
+ it('drops requests with disallowed protocols in finalSendRequest', async function() {
+ const traceEvents = await modifyRedirectEvent(this,
+ events => events[events.length - 1], // Final SendRequest
+ 'chrome://settings');
+
+ await runHandlers(traceEvents);
+
+ const {byTime} = Trace.Handlers.ModelHandlers.NetworkRequests.data();
+ assert.lengthOf(byTime, 1);
+ });
+
+ it('sanitizes disallowed protocols in redirects array', async function() {
+ const traceEvents = await modifyRedirectEvent(this,
+ events => events[0], // First SendRequest (redirect)
+ 'chrome://privileged');
+
+ await runHandlers(traceEvents);
+
+ const {byTime} = Trace.Handlers.ModelHandlers.NetworkRequests.data();
+ assert.lengthOf(byTime, 2);
+ assert.strictEqual(byTime[1].args.data.redirects[0].url, '');
+ assert.strictEqual(byTime[1].args.data.redirects[1].url, 'http://localhost:3000/bar');
+ });
+
+ it('sanitizes disallowed protocols in stackTrace', async function() {
+ const rawEvents = await TraceLoader.rawEvents(this, 'network-requests-initiators.json.gz');
+ const traceEvents = [...rawEvents];
+
+ for (let i = 0; i < traceEvents.length; i++) {
+ const event = traceEvents[i];
+ if (Trace.Types.Events.isResourceSendRequest(event) && event.args.data.stackTrace) {
+ const hasPolymer = event.args.data.stackTrace.some(f => f.url.includes('desktop_polymer.js'));
+ if (hasPolymer) {
+ traceEvents[i] = updateResourceSendRequestData(event, {
+ stackTrace: event.args.data.stackTrace.map(frame => frame.url.includes('desktop_polymer.js') ?
+ {...frame, url: 'chrome://privileged-script'} :
+ frame),
+ });
+ }
+ }
+ }
+
+ await runHandlers(traceEvents);
+
+ const {byTime} = Trace.Handlers.ModelHandlers.NetworkRequests.data();
+ const event = byTime.find(event => event.ts === 1491681999060);
+ assert.exists(event);
+ assert.strictEqual(event.args.data.stackTrace![0].url, '');
+ });
+ });
});
async function runHandlers(events: readonly Trace.Types.Events.Event[]): Promise<void> {
@@ -477,6 +567,21 @@
await Trace.Handlers.ModelHandlers.NetworkRequests.finalize();
}
+function updateResourceSendRequestData(
+ event: Trace.Types.Events.ResourceSendRequest,
+ dataUpdates: Partial<Trace.Types.Events.ResourceSendRequest['args']['data']>): Trace.Types.Events.Event {
+ return {
+ ...event,
+ args: {
+ ...event.args,
+ data: {
+ ...event.args.data,
+ ...dataUpdates,
+ },
+ },
+ } as Trace.Types.Events.Event;
+}
+
function assertDataArgsStats<D extends keyof DataArgs>(
requests: Trace.Types.Events.SyntheticNetworkRequest[], url: string, stats: Map<D, DataArgs[D]>): void {
const request = requests.find(request => request.args.data.url === url);
diff --git a/front_end/models/trace/handlers/NetworkRequestsHandler.ts b/front_end/models/trace/handlers/NetworkRequestsHandler.ts
index 30ed3c3..7029b44 100644
--- a/front_end/models/trace/handlers/NetworkRequestsHandler.ts
+++ b/front_end/models/trace/handlers/NetworkRequestsHandler.ts
@@ -213,6 +213,14 @@
export async function finalize(): Promise<void> {
const {rendererProcessesByFrame} = metaHandlerData();
+
+ const allowedProtocols = [
+ 'blob:',
+ 'file:',
+ 'filesystem:',
+ 'http:',
+ 'https:',
+ ];
for (const [requestId, request] of requestMap.entries()) {
// If we have an incomplete set of events here, we choose to drop the network
// request rather than attempt to synthesize the missing data.
@@ -248,7 +256,7 @@
}
redirects.push({
- url: sendRequest.args.data.url,
+ url: allowedProtocols.some(p => sendRequest.args.data.url.startsWith(p)) ? sendRequest.args.data.url : '',
priority: sendRequest.args.data.priority,
requestMethod: sendRequest.args.data.requestMethod,
ts,
@@ -360,15 +368,7 @@
}
}
- // TODO: consider allowing chrome / about.
- const allowedProtocols = [
- 'blob:',
- 'file:',
- 'filesystem:',
- 'http:',
- 'https:',
- ];
- if (!allowedProtocols.some(p => firstSendRequest.args.data.url.startsWith(p))) {
+ if (!allowedProtocols.some(p => finalSendRequest.args.data.url.startsWith(p))) {
continue;
}
@@ -572,7 +572,11 @@
responseHeaders: request.receiveResponse?.args.data.headers ?? null,
fetchPriorityHint: finalSendRequest.args.data.fetchPriorityHint ?? 'auto',
initiator: finalSendRequest.args.data.initiator,
- stackTrace: finalSendRequest.args.data.stackTrace,
+ stackTrace: finalSendRequest.args.data.stackTrace?.map(
+ frame => ({
+ ...frame,
+ url: allowedProtocols.some(p => frame.url.startsWith(p)) ? frame.url : '',
+ })),
timing,
lrServerResponseTime,
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page