Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInformation disclosure in the Framework component in DevTools
ComponentCore
Bug ClassLogic Error
Tracker2027519
Fix commitd08a58588da7 (firefox) +77/-29
CISA KEVNot listed
CreditedTomoya Nakanishi
Disclosed2026-07-21

Changed Functions

FunctionChangeNotes
if
devtools/server/actors/source.js
modified

Files Changed

  • devtools/server/actors/source.js
diff --git a/devtools/server/actors/source.js b/devtools/server/actors/source.js
index 2566111d5f0..60f9541f7f6 100644
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -45,10 +45,23 @@ ChromeUtils.defineESModuleGetters(
 
 const windowsDrive = /^([a-zA-Z]:)/;
 
+/**
+ * Resolve the potentially relative sourceURL to an absolute URL, based on the
+ * base URL of the current target actor owning this source.
+ *
+ * @param {string} sourceURL
+ *        The sourceURL to resolve to an absolute URL. If the URL is already an
+ *        absolute URL, the same value will be returned.
+ * @param {TargetActor} targetActor
+ *        The target actor to use to extract the base URL.
+ *
+ * @return {string|null} The resolved source URL or null.
+ */
 function resolveSourceURL(sourceURL, targetActor) {
   if (sourceURL) {
     let baseURL;
     if (targetActor.window) {
+      // Bug 2048537: Should use baseURI here.
       baseURL = targetActor.window.location?.href;
     }
     // For worker, we don't have easy access to location,
@@ -56,6 +69,10 @@ function resolveSourceURL(sourceURL, targetActor) {
     if (targetActor.workerUrl) {
       baseURL = targetActor.workerUrl;
     }
+
+    // TODO: for absolute URLs, baseURL is ignored by URL.parse.
+    // We could check URL.canParse(sourceURL) early and skip the baseURL
+    // computation in most cases.
     const parsedURL = URL.parse(sourceURL, baseURL);
     if (parsedURL) {
       return parsedURL.href;
@@ -64,48 +81,75 @@ function resolveSourceURL(sourceURL, targetActor) {
 
   return null;
 }
+
+/**
+ * Return the source's full URL as a string, according to `//# sourceURL` pragma
+ * if present in the source.
+ *
+ * @param {Debugger.Source} source
+ *        The source for which we want to get the full/display URL.
+ * @param {TargetActor} targetActor
+ *        The target actor owning the thread actor responsible for this source.
+ *
+ * @returns {string|null} The full / display URL or null if it could not be
+ *         computed
+ */
 function getSourceURL(source, targetActor) {
+  let result = resolveSourceURL(source.displayURL, targetActor);
+  if (!result) {
+    result = getRealSourceURL(source, targetActor);
+  }
+
+  // Avoid returning empty string and return null if no URL is found
+  return result || null;
+}
+
+/**
+ * Return the source's full URL as a string, ignoring any `//# sourceURL` pragma
+ * present in the source.
+ *
+ * @param {Debugger.Source} source
+ *        The source for which we want to get the real URL.
+ * @param {TargetActor} targetActor
+ *        The target actor owning the thread actor responsible for this source.
+ *
+ * @returns {string|null} The real URL or null if it could not be computed.
+ */
+function getRealSourceURL(source, targetActor) {
   // Some eval sources have URLs, but we want to explicitly ignore those because
   // they are generally useless strings like "eval" or "debugger eval code".
-  let resourceURL = getDebuggerSourceURL(source) || "";
+  let url = getDebuggerSourceURL(source) || "";
 
   // Strip out eventual stack trace stored in Source's url.
   // (not clear if that still happens)
-  resourceURL = resourceURL.split(" -> ").pop();
+  url = url.split(" -> ").pop();
 
   // Debugger.Source.url attribute may be of the form:
   //   "http://example.com/foo line 10 > inlineScript"
   // because of the following function `js::FormatIntroducedFilename`:
   // https://searchfox.org/mozilla-central/rev/253ae246f642fe9619597f44de3b087f94e45a2d/js/src/vm/JSScript.cpp#1816-1846
   // This isn't so easy to reproduce, but browser_dbg-breakpoints-popup.js's testPausedInTwoPopups covers this
-  resourceURL = resourceURL.replace(/ line \d+ > .*$/, "");
-
-  // A "//# sourceURL=" pragma should basically be treated as a source file's
-  // full URL, so that is what we want to use as the base if it is present.
-  // If this is not an absolute URL, this will mean the maps in the file
-  // will not have a valid base URL, but that is up to tooling that
-  let result = resolveSourceURL(source.displayURL, targetActor);
-  if (!result) {
-    result = resolveSourceURL(resourceURL, targetActor) || resourceURL;
-
-    // In XPCShell tests, the source URL isn't actually a URL, it's a file path.
-    // That causes issues because "C:/folder/file.js" is parsed as a URL with
-    // "c:" as the URL scheme, which causes the drive letter to be unexpectedly
-    // lower-cased when the parsed URL is re-serialized. To avoid that, we
-    // detect that case and re-uppercase it again. This is a bit gross and
-    // ideally it seems like XPCShell tests should use file:// URLs for files,
-    // but alas they do not.
-    if (
-      resourceURL &&
-      resourceURL.match(windowsDrive) &&
-      result.slice(0, 2) == resourceURL.slice(0, 2).toLowerCase()
-    ) {
-      result = resourceURL.slice(0, 2) + result.slice(2);
-    }
+  url = url.replace(/ line \d+ > .*$/, "");
+
+  let sourceURL = resolveSourceURL(url, targetActor) || url;
+
+  // In XPCShell tests, the source URL isn't actually a URL, it's a file path.
+  // That causes issues because "C:/folder/file.js" is parsed as a URL with
+  // "c:" as the URL scheme, which causes the drive letter to be unexpectedly
+  // lower-cased when the parsed URL is re-serialized. To avoid that, we
+  // detect that case and re-uppercase it again. This is a bit gross and
+  // ideally it seems like XPCShell tests should use file:// URLs for files,
+  // but alas they do not.
+  if (
+    url &&
+    sourceURL &&
+    url.match(windowsDrive) &&
+    sourceURL.slice(0, 2) == url.slice(0, 2).toLowerCase()
+  ) {
+    sourceURL = url.slice(0, 2) + sourceURL.slice(2);
   }
 
-  // Avoid returning empty string and return null if no URL is found
-  return result || null;
+  return sourceURL || null;
 }
 
 /**
@@ -211,8 +255,12 @@ class SourceActor extends Actor {
       extensionName: this.extensionName,
       url: this.url,
       isBlackBoxed: this.sourcesManager.isBlackBoxed(this.url),
+      // Bug 1970319: only use the real URL for sourcemap resolution until
+      // requests can be handled in content.
+      // According to the spec (https://tc39.es/ecma426/#sec-linking-generated-code)
+      // sourceMapBaseURL should use the displayURL/sourceURL for inline sources.
       sourceMapBaseURL: getSourcemapBaseURL(
-        this.url,
+        getRealSourceURL(this._source, this.threadActor.targetActor),
         this.threadActor.targetActor.window
       ),
       sourceMapURL: source.sourceMapURL,
Loading diff…