Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in DevTools
DescriptionInsufficient validation of untrusted input in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker523248021
Fix commit23882774076c (devtools/devtools-frontend) +121/-12
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
for
front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
modified
if
front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
modified
it
front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
modified

Files Changed

  • front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
  • front_end/models/trace/handlers/NetworkRequestsHandler.ts
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.