Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
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
Tracker513770449
Fix commit951c1e9f60ad (devtools/devtools-frontend) +43/-35
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
RegisteredExtension
front_end/panels/common/ExtensionServer.ts
modified
constructor
front_end/panels/common/ExtensionServer.ts
modified
if
front_end/panels/common/ExtensionServer.ts
modified

Files Changed

  • front_end/core/host/InspectorFrontendHostAPI.ts
  • front_end/core/host/UserMetrics.ts
  • front_end/devtools_compatibility.js
  • front_end/panels/common/ExtensionServer.test.ts
  • front_end/panels/common/ExtensionServer.ts
From 951c1e9f60ad86eb0a6b247b6cdf8b2f6e3f301d Mon Sep 17 00:00:00 2001
From: Philip Pfaffe <[email protected]>
Date: Tue, 23 Jun 2026 10:36:05 +0000
Subject: [PATCH] Move chrome-extension access check out of eval

Fixed: 513770449
Change-Id: Iebc4ba6f5dfc9804ffd456d9c2756d2ec0cd6e5a
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7984249
Reviewed-by: Danil Somsikov <[email protected]>
Commit-Queue: Philip Pfaffe <[email protected]>
---

diff --git a/front_end/core/host/InspectorFrontendHostAPI.ts b/front_end/core/host/InspectorFrontendHostAPI.ts
index 627f62d..03a0a11 100644
--- a/front_end/core/host/InspectorFrontendHostAPI.ts
+++ b/front_end/core/host/InspectorFrontendHostAPI.ts
@@ -556,6 +556,5 @@
   LighthouseCategoryUsed = 'DevTools.LighthouseCategoryUsed',
   SwatchActivated = 'DevTools.SwatchActivated',
   BuiltInAiAvailability = 'DevTools.BuiltInAiAvailability',
-  ExtensionEvalTarget = 'DevTools.ExtensionEvalTarget',
   // LINT.ThenChange(/front_end/devtools_compatibility.js:EnumeratedHistogram)
 }
diff --git a/front_end/core/host/UserMetrics.ts b/front_end/core/host/UserMetrics.ts
index 59f17c8..882fcee 100644
--- a/front_end/core/host/UserMetrics.ts
+++ b/front_end/core/host/UserMetrics.ts
@@ -254,10 +254,6 @@
         'DevTools.Insights.ShortTeaserGenerationTime', timeInMilliseconds);
   }
 
-  extensionEvalTarget(target: ExtensionEvalTarget): void {
-    InspectorFrontendHostInstance.recordEnumeratedHistogram(
-        EnumeratedHistogram.ExtensionEvalTarget, target, ExtensionEvalTarget.MAX_VALUE);
-  }
 }
 
 /**
@@ -1197,10 +1193,3 @@
   DISABLED_NO_GPU = 9,
   MAX_VALUE = 10,
 }
-
-export const enum ExtensionEvalTarget {
-  WEB_PAGE = 0,
-  SAME_EXTENSION = 1,
-  OTHER_EXTENSION = 2,
-  MAX_VALUE = 3,
-}
diff --git a/front_end/devtools_compatibility.js b/front_end/devtools_compatibility.js
index 68e99f9..894969c 100644
--- a/front_end/devtools_compatibility.js
+++ b/front_end/devtools_compatibility.js
@@ -444,8 +444,7 @@
     TimelineNavigationSettingState: 'DevTools.TimelineNavigationSettingState',
     SyncSetting: 'DevTools.SyncSetting',
     SwatchActivated: 'DevTools.SwatchActivated',
-    BuiltInAiAvailability: 'DevTools.BuiltInAiAvailability',
-    ExtensionEvalTarget: 'DevTools.ExtensionEvalTarget'
+    BuiltInAiAvailability: 'DevTools.BuiltInAiAvailability'
     // LINT.ThenChange(/front_end/core/host/InspectorFrontendHostAPI.ts:EnumeratedHistogram)
   };
 
diff --git a/front_end/panels/common/ExtensionServer.test.ts b/front_end/panels/common/ExtensionServer.test.ts
index 85fc825..e30d14d 100644
--- a/front_end/panels/common/ExtensionServer.test.ts
+++ b/front_end/panels/common/ExtensionServer.test.ts
@@ -808,6 +808,36 @@
     assert.deepEqual(result.error?.details, ['Permission denied']);
   });
 
+  it('blocks evaluation on other extension execution contexts', async () => {
+    assert.isUndefined(context.chrome.devtools);
+
+    const parentFrameUrl = allowedUrl;
+    const parentFrame = await setUpFrame('parent', parentFrameUrl, undefined, parentFrameUrl);
+
+    // Yield to the microtask queue to allow the extension to be initialized.
+    await new Promise(r => setTimeout(r, 0));
+
+    // Create a non-default context with a different extension origin.
+    const otherExtensionOrigin = urlString`chrome-extension://other-extension`;
+    const runtimeModel = parentFrame.resourceTreeModel()?.target().model(SDK.RuntimeModel.RuntimeModel);
+    assert.exists(runtimeModel);
+    runtimeModel.executionContextCreated({
+      id: 1 as Protocol.Runtime.ExecutionContextId,
+      origin: otherExtensionOrigin,
+      name: otherExtensionOrigin,
+      uniqueId: otherExtensionOrigin,
+      auxData: {frameId: parentFrame.id, isDefault: false},
+    });
+    const result = await new Promise<{result: unknown, error?: {details: unknown[]}}>(
+        r => context.chrome.devtools?.inspectedWindow.eval(
+            // The typings don't match the implementation, so we need to cast to any here to make ts happy.
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            '4', {frameURL: parentFrameUrl, scriptExecutionContext: otherExtensionOrigin} as any,
+            (result, error) => r({result, error})));
+
+    assert.deepEqual(result.error?.details, ['Permission denied']);
+  });
+
   it('blocks evaluation on blocked sub-executioncontexts', async () => {
     assert.isUndefined(context.chrome.devtools);
 
diff --git a/front_end/panels/common/ExtensionServer.ts b/front_end/panels/common/ExtensionServer.ts
index ec2342a..fb7ea53 100644
--- a/front_end/panels/common/ExtensionServer.ts
+++ b/front_end/panels/common/ExtensionServer.ts
@@ -85,7 +85,8 @@
 }
 
 class RegisteredExtension {
-  constructor(readonly name: string, readonly hostsPolicy: HostsPolicy, readonly allowFileAccess: boolean) {
+  constructor(readonly origin: string, readonly name: string, readonly hostsPolicy: HostsPolicy,
+              readonly allowFileAccess: boolean) {
   }
 
   isAllowedOnTarget(inspectedURL?: Platform.DevToolsPath.UrlString): boolean {
@@ -104,6 +105,14 @@
       return false;
     }
 
+    if (parsedURL.protocol === 'chrome-extension:') {
+      if (parsedURL.origin !== this.origin) {
+        if (!Root.Runtime.hostConfig.extensionsOnChromeUrls?.enabled) {
+          return false;
+        }
+      }
+    }
+
     if (!ExtensionServer.canInspectURL(inspectedURL)) {
       return false;
     }
@@ -1388,7 +1397,8 @@
       const startPageURL = new URL((startPage));
       const extensionOrigin = startPageURL.origin;
       const name = extensionInfo.name || `Extension ${extensionOrigin}`;
-      const extensionRegistration = new RegisteredExtension(name, hostsPolicy, Boolean(extensionInfo.allowFileAccess));
+      const extensionRegistration =
+          new RegisteredExtension(extensionOrigin, name, hostsPolicy, Boolean(extensionInfo.allowFileAccess));
       if (!extensionRegistration.isAllowedOnTarget(inspectedURL)) {
         this.#pendingExtensions.push(extensionInfo);
         return;
@@ -1609,25 +1619,6 @@
       return this.status.E_FAILED('Permission denied');
     }
 
-    try {
-      const parsedUrl = new URL(frame.url);
-      let targetType = Host.UserMetrics.ExtensionEvalTarget.WEB_PAGE;
-      if (parsedUrl.protocol === 'chrome-extension:') {
-        if (parsedUrl.origin === securityOrigin) {
-          targetType = Host.UserMetrics.ExtensionEvalTarget.SAME_EXTENSION;
-        } else {
-          targetType = Host.UserMetrics.ExtensionEvalTarget.OTHER_EXTENSION;
-          if (!Root.Runtime.hostConfig.extensionsOnChromeUrls?.enabled) {
-            return this.status.E_FAILED(
-                'Access to extension URLs is restricted; use --extensions-on-chrome-urls to enable.');
-          }
-        }
-      }
-      Host.userMetrics.extensionEvalTarget(targetType);
-    } catch {
-      // Ignore invalid URLs.
-    }
-
     void context
         .evaluate(
             {
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.