Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionPrivilege escalation in the DevTools component
ComponentCore
Bug ClassLogic Error
Tracker2063652
Fix commit71b78e3206b8 (firefox) +42/-12
CISA KEVNot listed
CreditedFinn Westendorf
Disclosed2026-09-15

Files Changed

  • devtools/shared/network-observer/NetworkLocalMode.sys.mjs
diff --git a/devtools/shared/network-observer/NetworkLocalMode.sys.mjs b/devtools/shared/network-observer/NetworkLocalMode.sys.mjs
index f44c41a2117..1e71b8a1122 100644
--- a/devtools/shared/network-observer/NetworkLocalMode.sys.mjs
+++ b/devtools/shared/network-observer/NetworkLocalMode.sys.mjs
@@ -15,6 +15,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
     "resource://devtools/shared/network-observer/NetworkOverride.sys.mjs",
 });
 
+const xmlSerializer = new XMLSerializer();
+
 export const NetworkLocalMode = {
   /**
    * Intercept a early channel that hasn't established a remote connection yet,
@@ -91,8 +93,24 @@ function overrideChannelInto404(channel) {
 
   replacedHttpResponse.responseStatus = 404;
   replacedHttpResponse.responseStatusText = "404 Not Found";
-  const body = `<h1>DevTools Local Mode</h1><p>No local file for: ${channel.URI.filePath}</p>`;
-  replacedHttpResponse.responseBody = body;
+
+  // We don't have access to a document, and we can't create a DocumentFragment from here,
+  // so use DOMParser to create a new document that we can use to build our listing page.
+  const doc = new DOMParser().parseFromString(
+    "<!DOCTYPE html><html/>",
+    "text/html"
+  );
+
+  // Compute a simple, but meaningful HTML file for the error
+  const h1 = doc.createElement("h1");
+  h1.append("DevTools Local Mode");
+  const p = doc.createElement("p");
+  p.textContent = `No local file for: ${channel.URI.filePath}`;
+  doc.body.append(h1, p);
+
+  const serializedDoc = xmlSerializer.serializeToString(doc);
+
+  replacedHttpResponse.responseBody = serializedDoc;
 
   channel
     .QueryInterface(Ci.nsIHttpChannelInternal)
@@ -123,8 +141,18 @@ async function overrideChannelWithDirectoryListing(channel, folderPath, file) {
   // the directory listing content
   channel.suspend();
 
+  // We don't have access to a document, and we can't create a DocumentFragment from here,
+  // so use DOMParser to create a new document that we can use to build our listing page.
+  const doc = new DOMParser().parseFromString(
+    "<!DOCTYPE html><html/>",
+    "text/html"
+  );
+
   // Compute a simple, but meaningful HTML file for the listing
-  const links = [];
+  const h2 = doc.createElement("h2");
+  h2.append(PathUtils.filename(file.path));
+  const ul = doc.createElement("ul");
+
   const children = await IOUtils.getChildren(file.path);
   for (const childPath of children.sort()) {
     const filename = PathUtils.filename(childPath);
@@ -133,16 +161,18 @@ async function overrideChannelWithDirectoryListing(channel, folderPath, file) {
       folderPath +
       (!folderPath || folderPath.endsWith("/") ? "" : "/") +
       filename;
-    links.push(`<li><a href="${absolutePath}">${filename}</a></li>`);
+    const li = doc.createElement("li");
+    const a = doc.createElement("a");
+    a.href = absolutePath;
+    a.append(filename);
+    li.append(a);
+    ul.append(li);
   }
-  const body =
-    "<h2>" +
-    PathUtils.filename(file.path) +
-    ":</h2><ul>" +
-    links.join("") +
-    "</ul>";
-
-  replacedHttpResponse.responseBody = body;
+  doc.body.append(h2, ul);
+
+  const serializedDoc = xmlSerializer.serializeToString(doc);
+
+  replacedHttpResponse.responseBody = serializedDoc;
 
   channel
     .QueryInterface(Ci.nsIHttpChannelInternal)
Loading diff…