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 <[email protected]> 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 <[email protected]> Reviewed-by: Yang Guo <[email protected]> Commit-Queue: Yang Guo <[email protected]> --- 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