Chrome · DevTools
CVE-2026-17786
Logic Error in DevTools
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
RegisteredExtensionfront_end/panels/common/ExtensionServer.ts |
modified | |
constructorfront_end/panels/common/ExtensionServer.ts |
modified | |
iffront_end/panels/common/ExtensionServer.ts |
modified |
Files Changed
front_end/core/host/InspectorFrontendHostAPI.tsfront_end/core/host/UserMetrics.tsfront_end/devtools_compatibility.jsfront_end/panels/common/ExtensionServer.test.tsfront_end/panels/common/ExtensionServer.ts
Patch
From 951c1e9f60ad86eb0a6b247b6cdf8b2f6e3f301d Mon Sep 17 00:00:00 2001
From: Philip Pfaffe <pfaffe@chromium.org>
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 <dsv@chromium.org>
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
---
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.
References
On This Page