Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionPrivilege escalation in the Debugger component
ComponentCore
Bug ClassLogic Error
Tracker2023753
Fix commitb6f6feefc897 (firefox) +54/-30
CISA KEVNot listed
CreditedTomoya Nakanishi
Disclosed2026-04-21

Changed Functions

FunctionChangeNotes
constructor
devtools/client/debugger/src/components/QuickOpenModal.js
modified
renderHighlight
devtools/client/debugger/src/components/QuickOpenModal.js
modified
highlightQueryMatches
devtools/client/debugger/src/components/QuickOpenModal.js
modified
if
devtools/client/debugger/src/components/QuickOpenModal.js
modified

Files Changed

  • devtools/client/debugger/src/.eslintrc.mjs
  • devtools/client/debugger/src/components/QuickOpenModal.js
  • devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
diff --git a/devtools/client/debugger/src/.eslintrc.mjs b/devtools/client/debugger/src/.eslintrc.mjs
index f940fd0f3cf..11f35cce6e6 100644
--- a/devtools/client/debugger/src/.eslintrc.mjs
+++ b/devtools/client/debugger/src/.eslintrc.mjs
@@ -47,6 +47,8 @@ export default [
         process: true,
         global: true,
         L10N: true,
+        // TODO: Add this to the main ESlint globals Bug 2025542
+        Sanitizer: true,
       },
     },
     rules: {
diff --git a/devtools/client/debugger/src/components/QuickOpenModal.js b/devtools/client/debugger/src/components/QuickOpenModal.js
index df0c68ec8eb..ded47cceb6d 100644
--- a/devtools/client/debugger/src/components/QuickOpenModal.js
+++ b/devtools/client/debugger/src/components/QuickOpenModal.js
@@ -2,11 +2,12 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
-import React, { Component } from "devtools/client/shared/vendor/react";
-import { div } from "devtools/client/shared/vendor/react-dom-factories";
+import React, {
+  Component,
+  createRef,
+} from "devtools/client/shared/vendor/react";
 import PropTypes from "devtools/client/shared/vendor/react-prop-types";
 import { connect } from "devtools/client/shared/vendor/react-redux";
-import { basename } from "../utils/path";
 import { createLocation } from "../utils/location";
 
 const fuzzyAldrin = require("resource://devtools/client/shared/vendor/fuzzaldrin-plus.js");
@@ -59,6 +60,7 @@ export class QuickOpenModal extends Component {
   constructor(props) {
     super(props);
     this.state = { results: null, selectedIndex: 0 };
+    this.resultListRef = createRef();
   }
 
   static get propTypes() {
@@ -222,20 +224,14 @@ export class QuickOpenModal extends Component {
 
       if (query == "" && !this.isShortcutQuery()) {
         this.showTopSources();
-        return;
-      }
-
-      if (this.isSymbolSearch()) {
+      } else if (this.isSymbolSearch()) {
         await this.searchSymbols(query);
-        return;
-      }
-
-      if (this.isShortcutQuery()) {
+      } else if (this.isShortcutQuery()) {
         this.searchShortcuts(query);
-        return;
+      } else {
+        this.searchSources(query);
       }
-
-      this.searchSources(query);
+      this.highlightQueryMatches(this.props.query);
     } catch (e) {
       // Due to throttling this might get scheduled after the component and the
       // toolbox are destroyed.
@@ -391,23 +387,31 @@ export class QuickOpenModal extends Component {
   isSourcesQuery = () => this.props.searchType === "sources";
   isSourceSearch = () => this.isSourcesQuery() || this.isGotoSourceQuery();
 
-  /* eslint-disable react/no-danger */
-  renderHighlight(candidateString, query) {
+  highlightQueryMatches(query) {
     const options = {
       wrap: {
         tagOpen: '<mark class="highlight">',
         tagClose: "</mark>",
       },
     };
-    const html = fuzzyAldrin.wrap(candidateString, query, options);
-    return div({
-      dangerouslySetInnerHTML: {
-        __html: html,
-      },
-    });
+    if (this.resultListRef.current) {
+      const domEl = this.resultListRef.current.ref.current;
+      for (const titleNode of domEl.querySelectorAll(".title")) {
+        const htmlString = fuzzyAldrin.wrap(
+          titleNode.innerText,
+          query,
+          options
+        );
+        const sanitizer = new Sanitizer({
+          elements: ["mark"],
+          attributes: ["class"],
+        });
+        titleNode.setHTML(htmlString, { sanitizer });
+      }
+    }
   }
 
-  highlightMatching = (query, results) => {
+  renderResults = (query, results) => {
     let newQuery = query;
     if (newQuery === "") {
       return results;
@@ -418,11 +422,7 @@ export class QuickOpenModal extends Component {
       if (typeof result.title == "string") {
         return {
           ...result,
-          title: this.renderHighlight(
-            result.title,
-            basename(newQuery),
-            "title"
-          ),
+          title: result.title,
         };
       }
       return result;
@@ -451,7 +451,7 @@ export class QuickOpenModal extends Component {
     const { query } = this.props;
     const { selectedIndex, results } = this.state;
 
-    const items = this.highlightMatching(query, results || []);
+    const items = this.renderResults(query, results || []);
     const expanded = !!items && !!items.length;
     return React.createElement(
       Modal,
@@ -484,7 +484,7 @@ export class QuickOpenModal extends Component {
           items,
           selected: selectedIndex,
           selectItem: this.selectResultItem,
-          ref: "resultList",
+          ref: this.resultListRef,
           expanded,
           ...(this.isSourceSearch() ? SIZE_BIG : SIZE_DEFAULT),
         })
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js b/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
index b32febf51a3..0b77e952211 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
@@ -94,6 +94,18 @@ add_task(async function () {
   pressKey(dbg, "Escape");
   assertQuickOpenDisabled(dbg);
 
+  info("Test that the highlighted result matches match the query");
+  await quickOpen(dbg, "sw");
+  await waitForResults(dbg, [
+    "script-switching-01.js",
+    "script-switching-02.js",
+  ]);
+  await assertHighlightMatches(dbg, 1, "sw");
+  await assertHighlightMatches(dbg, 2, "sw");
+  EventUtils.sendString("i");
+  await assertHighlightMatches(dbg, 1, "swi");
+  pressKey(dbg, "Escape");
+
   info("Testing goto line:column");
   assertLine(dbg, 0);
   assertColumn(dbg, 1);
@@ -171,3 +183,13 @@ async function assertResultIsTab(dbg, index) {
     "Result should be a tab"
   );
 }
+
+async function assertHighlightMatches(dbg, resultIndex, expectedMatchText) {
+  const el = await findResultEl(dbg, resultIndex);
+  const highlight = await waitForElementWithSelector(dbg, "mark.highlight");
+  ok(el && !!highlight, "The query match is highlighted");
+  await waitUntil(
+    () => el.querySelector("mark.highlight").innerText == expectedMatchText
+  );
+  ok(true, "The highlighted text matches the query text");
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js b/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
index b32febf51a3..0b77e952211 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
@@ -94,6 +94,18 @@ add_task(async function () {
   pressKey(dbg, "Escape");
   assertQuickOpenDisabled(dbg);
 
+  info("Test that the highlighted result matches match the query");
+  await quickOpen(dbg, "sw");
+  await waitForResults(dbg, [
+    "script-switching-01.js",
+    "script-switching-02.js",
+  ]);
+  await assertHighlightMatches(dbg, 1, "sw");
+  await assertHighlightMatches(dbg, 2, "sw");
+  EventUtils.sendString("i");
+  await assertHighlightMatches(dbg, 1, "swi");
+  pressKey(dbg, "Escape");
+
   info("Testing goto line:column");
   assertLine(dbg, 0);
   assertColumn(dbg, 1);
@@ -171,3 +183,13 @@ async function assertResultIsTab(dbg, index) {
     "Result should be a tab"
   );
 }
+
+async function assertHighlightMatches(dbg, resultIndex, expectedMatchText) {
+  const el = await findResultEl(dbg, resultIndex);
+  const highlight = await waitForElementWithSelector(dbg, "mark.highlight");
+  ok(el && !!highlight, "The query match is highlighted");
+  await waitUntil(
+    () => el.querySelector("mark.highlight").innerText == expectedMatchText
+  );
+  ok(true, "The highlighted text matches the query text");
+}
Loading diff…