Low chrome Logic Error 🔧 Commit mapped

Overview

Low
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
Tracker513768645
Fix commit88681798359e (devtools/devtools-frontend) +59/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
front_end/ui/helpers/OpenInNewTab.ts
modified
if
front_end/ui/kit/link/Link.ts
modified

Files Changed

  • front_end/panels/application/ServiceWorkersView.ts
  • front_end/panels/application/components/BackForwardCacheView.ts
  • front_end/panels/settings/components/SyncSection.ts
  • front_end/ui/helpers/OpenInNewTab.test.ts
  • front_end/ui/helpers/OpenInNewTab.ts
  • front_end/ui/kit/link/Link.test.ts
  • front_end/ui/kit/link/Link.ts
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.