Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in Extensions
DescriptionMissing authorization in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker523313374
Fix commitecaa2e8404a1 (devtools/devtools-frontend) +150/-44
CISA KEVNot listed
CreditedMicrosoft Edge
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
if
front_end/panels/common/ExtensionServer.test.ts
modified

Files Changed

  • front_end/models/bindings/CSSWorkspaceBinding.test.ts
  • front_end/models/bindings/CSSWorkspaceBinding.ts
  • front_end/models/bindings/CompilerScriptMapping.ts
  • front_end/models/bindings/DebuggerWorkspaceBinding.ts
  • front_end/models/bindings/SASSSourceMapping.ts
  • front_end/panels/common/ExtensionServer.test.ts
From ecaa2e8404a1903859091b66a93ccf58babf1da6 Mon Sep 17 00:00:00 2001
From: David Garber <[email protected]>
Date: Fri, 07 Aug 2026 15:06:28 -0700
Subject: [PATCH] onAttachSourceMapToResource to throw an error if disallowed sourceMapURL

New unit tests confirm undesired access (i.e. they fail), prior to fix,
and then validate fix.

Bug: 523313374
Change-Id: Ic3f8d44f99067f1de900b94bd629bf7c06cc7e07
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7934689
Commit-Queue: David Garber <[email protected]>
Reviewed-by: Philip Pfaffe <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
---

diff --git a/front_end/models/bindings/CSSWorkspaceBinding.test.ts b/front_end/models/bindings/CSSWorkspaceBinding.test.ts
index 0343af4..1dc4789 100644
--- a/front_end/models/bindings/CSSWorkspaceBinding.test.ts
+++ b/front_end/models/bindings/CSSWorkspaceBinding.test.ts
@@ -124,6 +124,8 @@
     assert.exists(scssUISourceCode);
 
     await cssWorkspaceBinding.pendingLiveLocationChangesPromise();
+    assert.deepEqual(cssWorkspaceBinding.sourceMapURLsForUISourceCode(scssUISourceCode),
+                     [urlString`http://example.com/example.css.map`]);
 
     const header = cssModel.styleSheetHeaderForId(headerPayload.styleSheetId);
     assert.exists(header);
diff --git a/front_end/models/bindings/CSSWorkspaceBinding.ts b/front_end/models/bindings/CSSWorkspaceBinding.ts
index 1c31529..103a236 100644
--- a/front_end/models/bindings/CSSWorkspaceBinding.ts
+++ b/front_end/models/bindings/CSSWorkspaceBinding.ts
@@ -143,6 +143,10 @@
     }
     return rawLocations;
   }
+
+  sourceMapURLsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): Platform.DevToolsPath.UrlString[] {
+    return SASSSourceMapping.sourceMapURLsForUISourceCode(uiSourceCode);
+  }
 }
 
 export interface SourceMapping {
diff --git a/front_end/models/bindings/CompilerScriptMapping.ts b/front_end/models/bindings/CompilerScriptMapping.ts
index a38439e..cc5b05c 100644
--- a/front_end/models/bindings/CompilerScriptMapping.ts
+++ b/front_end/models/bindings/CompilerScriptMapping.ts
@@ -577,6 +577,10 @@
     return scripts;
   }
 
+  sourceMapURLsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): Platform.DevToolsPath.UrlString[] {
+    return [...this.#uiSourceCodeToSourceMaps.get(uiSourceCode)].map(sourceMap => sourceMap.url());
+  }
+
   private sourceMapAttachedForTest(_sourceMap: SDK.SourceMap.SourceMap): void {
   }
 
diff --git a/front_end/models/bindings/DebuggerWorkspaceBinding.ts b/front_end/models/bindings/DebuggerWorkspaceBinding.ts
index 3c73a0b..cef7a5f 100644
--- a/front_end/models/bindings/DebuggerWorkspaceBinding.ts
+++ b/front_end/models/bindings/DebuggerWorkspaceBinding.ts
@@ -463,6 +463,14 @@
     return [...scripts];
   }
 
+  sourceMapURLsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): Platform.DevToolsPath.UrlString[] {
+    const urls = new Set<Platform.DevToolsPath.UrlString>();
+    for (const modelData of this.#debuggerModelToData.values()) {
+      modelData.compilerMapping.sourceMapURLsForUISourceCode(uiSourceCode).forEach(url => urls.add(url));
+    }
+    return [...urls];
+  }
+
   supportsConditionalBreakpoints(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
     const scripts = this.pluginManager.scriptsForUISourceCode(uiSourceCode);
     return scripts.every(script => script.isJavaScript());
diff --git a/front_end/models/bindings/SASSSourceMapping.ts b/front_end/models/bindings/SASSSourceMapping.ts
index fe192b2..d287d85 100644
--- a/front_end/models/bindings/SASSSourceMapping.ts
+++ b/front_end/models/bindings/SASSSourceMapping.ts
@@ -140,6 +140,12 @@
     return [];
   }
 
+  static sourceMapURLsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode):
+      Platform.DevToolsPath.UrlString[] {
+    const binding = uiSourceCodeToBinding.get(uiSourceCode);
+    return binding?.getReferringSourceMaps().map(sourceMap => sourceMap.url()) ?? [];
+  }
+
   dispose(): void {
     Common.EventTarget.removeEventListeners(this.#eventListeners);
     this.#project.dispose();
diff --git a/front_end/panels/common/ExtensionServer.test.ts b/front_end/panels/common/ExtensionServer.test.ts
index 5ac6417..3927866 100644
--- a/front_end/panels/common/ExtensionServer.test.ts
+++ b/front_end/panels/common/ExtensionServer.test.ts
@@ -923,7 +923,7 @@
 
     sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
         .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
-                                          {scriptsForUISourceCode: []}));
+                                          {scriptsForUISourceCode: [], sourceMapURLsForUISourceCode: []}));
     const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
         Workspace.Workspace.WorkspaceImpl.instance(), target.id(), Workspace.Workspace.projectTypes.Network, '',
         false /* isServiceProject */);
@@ -962,7 +962,7 @@
 
     sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
         .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
-                                          {scriptsForUISourceCode: []}));
+                                          {scriptsForUISourceCode: [], sourceMapURLsForUISourceCode: []}));
 
     const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
         Workspace.Workspace.WorkspaceImpl.instance(), blockedTarget.id(), Workspace.Workspace.projectTypes.Network, '',
@@ -990,6 +990,7 @@
     const script = sinon.createStubInstance(SDK.Script.Script, {target, contentURL: blockedUrl});
     script.hasSourceURL = true;
     const workspaceBinding = sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding);
+    workspaceBinding.sourceMapURLsForUISourceCode.returns([]);
     workspaceBinding.scriptsForUISourceCode.callsFake(uiSourceCode => {
       if (uiSourceCode.contentURL() === blockedUrl) {
         return [script];
@@ -1017,6 +1018,7 @@
     script.hasSourceURL = true;
     script.embedderName.returns(blockedUrl);
     const workspaceBinding = sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding);
+    workspaceBinding.sourceMapURLsForUISourceCode.returns([]);
     workspaceBinding.scriptsForUISourceCode.callsFake(uiSourceCode => {
       if (uiSourceCode.contentURL() === allowedUrl) {
         return [script];
@@ -1035,6 +1037,24 @@
     assert.deepEqual(resources.map(r => r.url), []);
   });
 
+  it('blocks CSS sources originating from a blocked source map URL', async () => {
+    const target = getBackend(context).createTarget({id: 'target' as Protocol.Target.TargetID});
+    target.setInspectedURL(allowedUrl);
+
+    sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
+        .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
+                                          {scriptsForUISourceCode: [], sourceMapURLsForUISourceCode: []}));
+    const cssWorkspaceBinding = Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance();
+    (cssWorkspaceBinding.sourceMapURLsForUISourceCode as sinon.SinonStub).returns([blockedUrl]);
+    const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
+        Workspace.Workspace.WorkspaceImpl.instance(), target.id(), Workspace.Workspace.projectTypes.Network, '',
+        false /* isServiceProject */);
+    await createUISourceCode(project, allowedUrl, Common.ResourceType.resourceTypes.SourceMapStyleSheet);
+
+    const resources = await context.chrome.devtools!.inspectedWindow.getResources();
+    assert.deepEqual(resources.map(resource => resource.url), []);
+  });
+
   it('allows scripts with sourceURL comments if the embedderName is not a URL', async () => {
     const target = getBackend(context).createTarget({id: 'target' as Protocol.Target.TargetID});
     target.setInspectedURL(allowedUrl);
@@ -1043,6 +1063,7 @@
     script.hasSourceURL = true;
     script.embedderName.returns(urlString`eval`);
     const workspaceBinding = sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding);
+    workspaceBinding.sourceMapURLsForUISourceCode.returns([]);
     workspaceBinding.scriptsForUISourceCode.callsFake(uiSourceCode => {
       if (uiSourceCode.contentURL() === allowedUrl) {
         return [script];
@@ -1306,7 +1327,7 @@
 
     sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
         .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
-                                          {scriptsForUISourceCode: []}));
+                                          {scriptsForUISourceCode: [], sourceMapURLsForUISourceCode: []}));
     const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
         Workspace.Workspace.WorkspaceImpl.instance(), target.id(), Workspace.Workspace.projectTypes.Network, '',
         false /* isServiceProject */);
@@ -1652,6 +1673,47 @@
   }
 }
 
+async function createScriptResource(
+    context: ExtensionContext,
+    options?: {
+      scriptUrl?: Platform.DevToolsPath.UrlString,
+      scriptContent?: string,
+      inspectedUrl?: Platform.DevToolsPath.UrlString,
+    },
+    ): Promise<{
+  target: SDK.Target.Target,
+  currentScript: SDK.Script.Script,
+  scriptResource: Chrome.DevTools.Resource,
+}> {
+  const scriptUrl = options?.scriptUrl ?? urlString`http://example.com/script.js`;
+  const scriptContent =
+      options?.scriptContent ?? 'function f(x) { console.log(x); } function ignore(y){ console.log(y); }';
+
+  const backend = getBackend(context);
+  mockResourceTree(backend.cdpConnection);
+  const target = backend.createTarget({type: SDK.Target.Type.FRAME});
+  if (options?.inspectedUrl) {
+    target.setInspectedURL(options.inspectedUrl);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/front_end/testing/ExtensionHelpers.ts b/front_end/testing/ExtensionHelpers.ts
index dafde42..f52e5aa 100644
--- a/front_end/testing/ExtensionHelpers.ts
+++ b/front_end/testing/ExtensionHelpers.ts
@@ -7,6 +7,7 @@
 import type {Chrome} from '../../extension-api/ExtensionAPI.js';
 import * as Host from '../core/host/host.js';
 import * as SDK from '../core/sdk/sdk.js';
+import * as Bindings from '../models/bindings/bindings.js';
 import type * as Extensions from '../models/extensions/extensions.js';
 import * as Logs from '../models/logs/logs.js';
 import * as Workspace from '../models/workspace/workspace.js';
@@ -78,6 +79,10 @@
     context.backend = backend;
     sinon.stub(Workspace.Workspace.WorkspaceImpl, 'instance').returns(backend.universe.workspace);
     sinon.stub(SDK.TargetManager.TargetManager, 'instance').returns(backend.universe.targetManager);
+    const cssWorkspaceBinding = sinon.createStubInstance(Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding, {
+      sourceMapURLsForUISourceCode: [],
+    });
+    sinon.stub(Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding, 'instance').returns(cssWorkspaceBinding);
 
     const networkLog = new Logs.NetworkLog.NetworkLog(backend.universe.targetManager, backend.universe.settings);
     sinon.stub(Logs.NetworkLog.NetworkLog, 'instance').returns(networkLog);
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.