Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in DevTools
DescriptionInsufficient policy enforcement in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker513754837
Fix commit921932ce2b7c (devtools/devtools-frontend) +216/-86
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • front_end/panels/common/ExtensionServer.test.ts
From 921932ce2b7c0d40c8843f23f07f94e03155f2eb Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Wed, 24 Jun 2026 12:24:31 -0700
Subject: [PATCH] Block access to resources from blocked targets.

This change updates the ExtensionServer to prevent extensions from accessing network requests and resources if the associated target's inspected URL is blocked, even if the resource or request URL itself is on an allowed domain. This is applied to HAR entries, resource content fetching, and network request finished events. New tests are added to verify these security constraints.

Bug: 513754837
Change-Id: Iaced3448d56b24bd7e55268569303257cd36daf9
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7985744
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
---

diff --git a/front_end/panels/common/ExtensionServer.test.ts b/front_end/panels/common/ExtensionServer.test.ts
index c26f1e4..6606bac 100644
--- a/front_end/panels/common/ExtensionServer.test.ts
+++ b/front_end/panels/common/ExtensionServer.test.ts
@@ -93,11 +93,14 @@
       const target = createTarget({url});
       const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
       assert.isNotNull(resourceTreeModel);
-      await resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
+      if (!resourceTreeModel.cachedResourcesLoaded()) {
+        await resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
+      }
       target.setInspectedURL(url);
-      resourceTreeModel.mainFrame?.addResource(new SDK.Resource.Resource(
-          resourceTreeModel, null, url, url, null, null, Common.ResourceType.resourceTypes.Document, 'application/text',
-          null, null));
+      const mainFrame = getMainFrame(target, {url});
+      mainFrame.addResource(new SDK.Resource.Resource(resourceTreeModel, null, url, url, null, null,
+                                                      Common.ResourceType.resourceTypes.Document, 'application/text',
+                                                      null, null));
       return target;
     });
 
@@ -148,8 +151,8 @@
            // create a mock uiSourceCode for the sourceMap script
            const scriptUrl = urlString`https://example.com/foo.js.map/foo.js`;
            project.addUISourceCode(
-               new Workspace.UISourceCode.UISourceCode(
-                   project, scriptUrl, Common.ResourceType.resourceTypes.SourceMapScript),
+               new Workspace.UISourceCode.UISourceCode(project, scriptUrl,
+                                                       Common.ResourceType.resourceTypes.SourceMapScript),
            );
            const uiSourceCode = project.uiSourceCodeForURL(scriptUrl);
            assert.exists(uiSourceCode);
@@ -195,11 +198,14 @@
       const stubScript = sinon.createStubInstance(SDK.Script.Script);
       // @ts-expect-error
       stubScript.buildId = 'my-build-id';
+      stubScript.target.returns(target);
+      stubScript.contentURL.returns(urlString`http://example.com/index.js`);
+      stubScript.hasSourceURL = false;
       sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(), 'scriptsForUISourceCode')
           .returns([stubScript]);
       project.addUISourceCode(
-          new Workspace.UISourceCode.UISourceCode(
-              project, urlString`http://example.com/index.js`, Common.ResourceType.resourceTypes.Script),
+          new Workspace.UISourceCode.UISourceCode(project, urlString`http://example.com/index.js`,
+                                                  Common.ResourceType.resourceTypes.Script),
       );
 
       const resources =
@@ -681,9 +687,32 @@
     }
   });
 
-  async function setUpFrame(
-      name: string, url: Platform.DevToolsPath.UrlString, parentFrame?: SDK.ResourceTreeModel.ResourceTreeFrame,
-      executionContextOrigin?: Platform.DevToolsPath.UrlString) {
+  it('does not include requests from blocked targets in the HAR entries even if request URL is allowed', async () => {
+    Logs.NetworkLog.NetworkLog.instance();
+
+    const parentFrameUrl = allowedUrl;
+    const childFrameUrl = blockedUrl;
+    const parentFrame = await setUpFrame('parent', parentFrameUrl);
+    const childFrame = await setUpFrame('child', childFrameUrl, parentFrame);
+
+    const childTarget = childFrame.resourceTreeModel()?.target();
+    assert.exists(childTarget);
+    childTarget.setInspectedURL(blockedUrl);  // Explicitly set to blocked URL
+
+    const networkManager = childTarget.model(SDK.NetworkManager.NetworkManager);
+    assert.exists(networkManager);
+
+    const frameId = 'child-frame-id' as Protocol.Page.FrameId;
+    const requestUrl = urlString`${allowedUrl}?fromBlockedTarget`;
+    createRequest(networkManager, frameId, 'request-from-blocked-target' as Protocol.Network.RequestId, requestUrl);
+
+    const result = await context.chrome.devtools!.network.getHAR() as TestHARLog;
+    assert.notExists(result.entries.find(e => e.request.url === requestUrl));
+  });
+
+  async function setUpFrame(name: string, url: Platform.DevToolsPath.UrlString,
+                            parentFrame?: SDK.ResourceTreeModel.ResourceTreeFrame,
+                            executionContextOrigin?: Platform.DevToolsPath.UrlString) {
     const parentTarget = parentFrame?.resourceTreeModel()?.target();
     const target = createTarget({id: `${name}-target-id` as Protocol.Target.TargetID, parentTarget});
     const frame = parentFrame ? await addChildFrame(target, {url}) : getMainFrame(target, {url});
@@ -712,8 +741,8 @@
     await setUpFrame('child', childFrameUrl, parentFrame);
 
     const result = await new Promise<{result: unknown, error?: {details: unknown[]}}>(
-        r => context.chrome.devtools?.inspectedWindow.eval(
-            '4', {frameURL: childFrameUrl}, (result, error) => r({result, error})));
+        r => context.chrome.devtools?.inspectedWindow.eval('4', {frameURL: childFrameUrl},
+                                                           (result, error) => r({result, error})));
 
     assert.deepEqual(result.error?.details, ['Permission denied']);
   });
@@ -849,15 +878,15 @@
     await setUpFrame('child', childFrameUrl, parentFrame, childExeContextOrigin);
 
     const result = await new Promise<{result: unknown, error?: {details: unknown[]}}>(
-        r => context.chrome.devtools?.inspectedWindow.eval(
-            '4', {frameURL: childFrameUrl}, (result, error) => r({result, error})));
+        r => context.chrome.devtools?.inspectedWindow.eval('4', {frameURL: childFrameUrl},
+                                                           (result, error) => r({result, error})));
 
     assert.deepEqual(result.error?.details, ['Permission denied']);
   });
 
-  async function createUISourceCode(
-      project: Bindings.ContentProviderBasedProject.ContentProviderBasedProject, url: Platform.DevToolsPath.UrlString,
-      contentType = Common.ResourceType.resourceTypes.Document) {
+  async function createUISourceCode(project: Bindings.ContentProviderBasedProject.ContentProviderBasedProject,
+                                    url: Platform.DevToolsPath.UrlString,
+                                    contentType = Common.ResourceType.resourceTypes.Document) {
     const mimeType = 'text/html';
     const dataProvider = () =>
         Promise.resolve(new TextUtils.ContentData.ContentData('content', /* isBase64 */ false, mimeType));
@@ -872,8 +901,8 @@
     target.setInspectedURL(allowedUrl);
 
     sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
-        .returns(sinon.createStubInstance(
-            Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, {scriptsForUISourceCode: []}));
+        .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
+                                          {scriptsForUISourceCode: []}));
     const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
         Workspace.Workspace.WorkspaceImpl.instance(), target.id(), Workspace.Workspace.projectTypes.Network, '',
         false /* isServiceProject */);
@@ -902,6 +931,37 @@
     ]);
   });
 
+  it('blocks getting resource contents on allowed urls if target is blocked', async () => {
+    const parentTarget = createTarget({id: 'parent' as Protocol.Target.TargetID});
+    parentTarget.setInspectedURL(allowedUrl);
+
+    const blockedTarget = createTarget({id: 'blocked-target' as Protocol.Target.TargetID});
+    blockedTarget.setInspectedURL(blockedUrl);
+    sinon.stub(blockedTarget, 'inspectedURL').returns(blockedUrl);
+
+    sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
+        .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
+                                          {scriptsForUISourceCode: []}));
+
+    const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
+        Workspace.Workspace.WorkspaceImpl.instance(), blockedTarget.id(), Workspace.Workspace.projectTypes.Network, '',
+        false /* isServiceProject */);
+
+    Bindings.NetworkProject.NetworkProject.setTargetForProject(project, blockedTarget);
+
+    const uniqueAllowedUrl = urlString`${allowedUrl}?uniqueResourceTest`;
+
+    // Stub targetForUISourceCode to ensure it returns blockedTarget for our unique URL
+    const targetForUISourceCodeStub = sinon.stub(Bindings.NetworkProject.NetworkProject, 'targetForUISourceCode');
+    targetForUISourceCodeStub.returns(blockedTarget);
+
+    await createUISourceCode(project, uniqueAllowedUrl);
+
+    assert.exists(context.chrome.devtools);
+    const resources = await context.chrome.devtools!.inspectedWindow.getResources();
+    assert.notExists(resources.find(r => r.url === uniqueAllowedUrl));
+  });
+
   it('allows arbitrary schemes in sourceURL comments, as long as the inspected target is allowed', async () => {
     const target = createTarget({id: 'target' as Protocol.Target.TargetID});
     target.setInspectedURL(allowedUrl);
@@ -928,10 +988,17 @@
     assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]);
   });
 
-  function createRequest(
-      networkManager: SDK.NetworkManager.NetworkManager, frameId: Protocol.Page.FrameId,
-      requestId: Protocol.Network.RequestId, url: Platform.DevToolsPath.UrlString): void {
+  const requestToManager = new Map<SDK.NetworkRequest.NetworkRequest, SDK.NetworkManager.NetworkManager>();
+
+  function createRequest(networkManager: SDK.NetworkManager.NetworkManager, frameId: Protocol.Page.FrameId,
+                         requestId: Protocol.Network.RequestId, url: Platform.DevToolsPath.UrlString): void {
+    if (!(SDK.NetworkManager.NetworkManager.forRequest as unknown as {isSinonProxy?: boolean}).isSinonProxy) {
+      requestToManager.clear();
+      sinon.stub(SDK.NetworkManager.NetworkManager, 'forRequest')
+          .callsFake(request => requestToManager.get(request) || null);
+    }
     const request = SDK.NetworkRequest.NetworkRequest.create(requestId, url, url, frameId, null, null, undefined);
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.