Chrome · DevTools
CVE-2026-17931
Logic Error in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
iffront_end/ui/helpers/OpenInNewTab.ts |
modified | |
iffront_end/ui/kit/link/Link.ts |
modified |
Files Changed
front_end/panels/application/ServiceWorkersView.tsfront_end/panels/application/components/BackForwardCacheView.tsfront_end/panels/settings/components/SyncSection.tsfront_end/ui/helpers/OpenInNewTab.test.tsfront_end/ui/helpers/OpenInNewTab.tsfront_end/ui/kit/link/Link.test.tsfront_end/ui/kit/link/Link.ts
Patch
From 88681798359ea08d46cb38e1db36f177ef98ef96 Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Thu, 11 Jun 2026 03:23:42 -0700
Subject: [PATCH] Introduce allowPrivileged option for opening links
This change adds an `allowPrivileged` option to `UIHelpers.openInNewTab` and the `Link` component. When true, this option permits navigating to `chrome://` URLs using CDP's `TargetAgent.invoke_createTarget`. Without this option, `chrome://` links will be opened via `InspectorFrontendHost.openInNewTab`.
Existing instances of `chrome://` links that should be opened via CDP have been updated to set `allowPrivileged` to true.
Bug: 513866380, 513768645, 513735900, 513864014, 513781245, 513838421
Change-Id: Ibbf8a0ed1733e96d8144faf5a6c83b63e38a0063
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7921415
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Yang Guo <yangguo@chromium.org>
---
diff --git a/front_end/panels/application/ServiceWorkersView.ts b/front_end/panels/application/ServiceWorkersView.ts
index f81f8f8..fec4a42 100644
--- a/front_end/panels/application/ServiceWorkersView.ts
+++ b/front_end/panels/application/ServiceWorkersView.ts
@@ -228,8 +228,8 @@
othersView.show(othersDiv);
const othersSection = othersView.appendSection(i18nString(UIStrings.serviceWorkersFromOtherOrigins));
const othersSectionRow = othersSection.appendRow();
- const seeOthers = Link.create(
- 'chrome://serviceworker-internals', i18nString(UIStrings.seeAllRegistrations), undefined, 'view-all');
+ const seeOthers = Link.create('chrome://serviceworker-internals', i18nString(UIStrings.seeAllRegistrations),
+ undefined, 'view-all', 0, /* allowPrivileged=*/ true);
othersSectionRow.appendChild(seeOthers);
this.toolbar.appendToolbarItem(
diff --git a/front_end/panels/application/components/BackForwardCacheView.ts b/front_end/panels/application/components/BackForwardCacheView.ts
index 5415b48..76cd6e7 100644
--- a/front_end/panels/application/components/BackForwardCacheView.ts
+++ b/front_end/panels/application/components/BackForwardCacheView.ts
@@ -341,7 +341,7 @@
const link = 'chrome://extensions/?id=' + explanation.context as Platform.DevToolsPath.UrlString;
// clang-format off
return html`${i18nString(UIStrings.blockingExtensionId)}
- <devtools-link .href=${link}>${explanation.context}</devtools-link>`;
+ <devtools-link .href=${link} allow-privileged>${explanation.context}</devtools-link>`;
// clang-format on
}
return nothing;
diff --git a/front_end/panels/settings/components/SyncSection.ts b/front_end/panels/settings/components/SyncSection.ts
index 6c82270..f4cd8bf 100644
--- a/front_end/panels/settings/components/SyncSection.ts
+++ b/front_end/panels/settings/components/SyncSection.ts
@@ -346,7 +346,7 @@
// TODO: investigate if /advance link is alive
const warningLink =
this.#syncInfo.isSyncActive ? 'chrome://settings/syncSetup/advanced' : 'chrome://settings/syncSetup';
- UIHelpers.openInNewTab(warningLink);
+ UIHelpers.openInNewTab(warningLink, /* allowPrivileged=*/ true);
event.consume();
}
diff --git a/front_end/ui/helpers/OpenInNewTab.test.ts b/front_end/ui/helpers/OpenInNewTab.test.ts
index b6b3232..3c564ed 100644
--- a/front_end/ui/helpers/OpenInNewTab.test.ts
+++ b/front_end/ui/helpers/OpenInNewTab.test.ts
@@ -147,14 +147,27 @@
});
describeWithMockConnection('chrome:// link', () => {
- it('call the correct API for chrome:// links', async () => {
+ it('calls invoke_createTarget for chrome:// links when privileged navigation is allowed', async () => {
const target = createTarget();
const spy = sinon.spy(target.targetAgent(), 'invoke_createTarget');
- openInNewTab('chrome://settings');
+ openInNewTab('chrome://settings', /* allowPrivileged=*/ true);
sinon.assert.calledOnce(spy);
assert.deepEqual(spy.firstCall.firstArg, {url: 'chrome://settings/'});
});
+
+ it('falls back to InspectorFrontendHost for chrome:// links when privileged navigation is not allowed',
+ async () => {
+ const target = createTarget();
+ const spy = sinon.spy(target.targetAgent(), 'invoke_createTarget');
+ const stub = sinon.stub(InspectorFrontendHostInstance, 'openInNewTab');
+
+ openInNewTab('chrome://settings');
+
+ sinon.assert.notCalled(spy);
+ sinon.assert.calledOnce(stub);
+ assert.strictEqual(stub.args[0][0], 'chrome://settings/');
+ });
});
});
diff --git a/front_end/ui/helpers/OpenInNewTab.ts b/front_end/ui/helpers/OpenInNewTab.ts
index cfa93f3..9032efc 100644
--- a/front_end/ui/helpers/OpenInNewTab.ts
+++ b/front_end/ui/helpers/OpenInNewTab.ts
@@ -57,15 +57,15 @@
* @throws TypeError if `url` is not a valid URL.
* @see https://en.wikipedia.org/wiki/UTM_parameters
*/
-export function openInNewTab(url: URL|string): void {
+export function openInNewTab(url: URL|string, allowPrivileged?: boolean): void {
url = new URL(url);
if (Common.ParsedURL.schemeIs(url, 'javascript:')) {
return;
}
// Navigating to a chrome:// link via a normal anchor doesn't work, so we "navigate"
- // there using CDP.
- if (Common.ParsedURL.schemeIs(url, 'chrome:')) {
+ // there using CDP if explicitly requested.
+ if (allowPrivileged && Common.ParsedURL.schemeIs(url, 'chrome:')) {
const rootTarget = SDK.TargetManager.TargetManager.instance().rootTarget();
if (rootTarget === null) {
return;
diff --git a/front_end/ui/kit/link/Link.test.ts b/front_end/ui/kit/link/Link.test.ts
index 9eaef54..c8af7d5 100644
--- a/front_end/ui/kit/link/Link.test.ts
+++ b/front_end/ui/kit/link/Link.test.ts
@@ -42,6 +42,27 @@
});
});
+ describe('allowPrivileged', () => {
+ it('sets allow-privileged attribute', () => {
+ const link = new Link();
+ link.allowPrivileged = true;
+ assert.isTrue(link.hasAttribute('allow-privileged'));
+ });
+
+ it('unsets allow-privileged attribute', () => {
+ const link = new Link();
+ link.allowPrivileged = true;
+ link.allowPrivileged = false;
+ assert.isFalse(link.hasAttribute('allow-privileged'));
+ });
+
+ it('reads allow-privileged attribute', () => {
+ const link = new Link();
+ link.setAttribute('allow-privileged', '');
+ assert.isTrue(link.allowPrivileged);
+ });
+ });
+
describe('visual logging', () => {
it('should default to empty link', () => {
const link = new Link();
diff --git a/front_end/ui/kit/link/Link.ts b/front_end/ui/kit/link/Link.ts
index b60cb58..d0d5e6a 100644
--- a/front_end/ui/kit/link/Link.ts
+++ b/front_end/ui/kit/link/Link.ts
@@ -60,7 +60,7 @@
return;
}
- UIHelpers.openInNewTab(href);
+ UIHelpers.openInNewTab(href, this.allowPrivileged);
event.consume();
}
@@ -73,6 +73,18 @@
this.setAttribute('href', href);
}
+ get allowPrivileged(): boolean {
+ return this.hasAttribute('allow-privileged');
+ }
+
+ set allowPrivileged(allowPrivileged: boolean) {
+ if (allowPrivileged) {
+ this.setAttribute('allow-privileged', '');
+ } else {
+ this.removeAttribute('allow-privileged');
+ }
+ }
+
get jslogContext(): string|null {
return this.getAttribute('jslogcontext');
}
@@ -98,7 +110,7 @@
oldValue: string|null,
newValue: string|null,
): void {
- if (oldValue !== newValue) {
+ if (oldValue === newValue) {
return;
}
if (name === 'jslogcontext') {
@@ -145,9 +157,11 @@
className?: string,
jsLogContext?: string,
tabindex = 0,
+ allowPrivileged = false,
): Link {
const link = new Link();
link.href = url as Platform.DevToolsPath.UrlString;
+ link.allowPrivileged = allowPrivileged;
linkText = linkText ?? url;
link.textContent = Platform.StringUtilities.trimMiddle(linkText, 150);
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