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 <[email protected]> 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 <[email protected]> Reviewed-by: Jack Franklin <[email protected]> Auto-Submit: Danil Somsikov <[email protected]> --- 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