Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInformation disclosure in the Form Autofill component
ComponentToolkit
Bug ClassLogic Error
Tracker2054776
Fix commitc4147681c178 (firefox) +72/-21
CISA KEVNot listed
CreditedThe Mozilla Fuzzing Team
Disclosed2026-08-18

Changed Functions

FunctionChangeNotes
isAllowedFieldname
toolkit/components/satchel/FormHistory.sys.mjs
modified
isAllowedEntry
toolkit/components/satchel/FormHistory.sys.mjs
modified
if
toolkit/components/satchel/FormHistory.sys.mjs
modified
if
toolkit/components/satchel/FormHistoryChild.sys.mjs
modified
if
toolkit/components/satchel/FormHistoryParent.sys.mjs
modified

Files Changed

  • toolkit/components/satchel/FormHistory.sys.mjs
  • toolkit/components/satchel/FormHistoryChild.sys.mjs
  • toolkit/components/satchel/FormHistoryParent.sys.mjs
diff --git a/toolkit/components/satchel/FormHistory.sys.mjs b/toolkit/components/satchel/FormHistory.sys.mjs
index f621105779b..a55bcb0e7ca 100644
--- a/toolkit/components/satchel/FormHistory.sys.mjs
+++ b/toolkit/components/satchel/FormHistory.sys.mjs
@@ -86,6 +86,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 const lazy = {};
 
 ChromeUtils.defineESModuleGetters(lazy, {
+  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
   Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
   setTimeout: "resource://gre/modules/Timer.sys.mjs",
 });
@@ -95,6 +96,8 @@ const DAY_IN_MS = 86400000; // 1 day in milliseconds
 const MAX_SEARCH_TOKENS = 10;
 const DB_FILENAME = "formhistory.sqlite";
 
+const MAX_FIELD_LENGTH = 200;
+
 var supportsDeletedTable = AppConstants.platform == "android";
 
 const wait = ms => new Promise(res => lazy.setTimeout(res, ms));
@@ -1015,6 +1018,45 @@ export let FormHistory = {
     return Prefs.get("enabled");
   },
 
+  /**
+   * Whether a fieldname is allowed to be written through FormHistory.
+   *
+   * @param {string} fieldname - the fieldname to test
+   * @returns {boolean}
+   */
+  isAllowedFieldname(fieldname) {
+    return !["searchbar-history"].includes(fieldname);
+  },
+
+  /**
+   * Whether an entry is eligible to be stored in form history.
+   *
+   * @param {string} name - the fieldname
+   * @param {string} value - the value
+   * @returns {boolean}
+   */
+  isAllowedEntry(name, value) {
+    if (typeof name != "string" || typeof value != "string") {
+      return false;
+    }
+
+    if (!this.isAllowedFieldname(name)) {
+      return false;
+    }
+
+    // Limit stored data to 200 characters.
+    if (name.length > MAX_FIELD_LENGTH || value.length > MAX_FIELD_LENGTH) {
+      return false;
+    }
+
+    // Don't save credit card numbers.
+    if (lazy.CreditCard.isValidNumber(value)) {
+      return false;
+    }
+
+    return true;
+  },
+
   async search(aSelectTerms, aSearchData, aRowFunc) {
     // if no terms selected, select everything
     if (!aSelectTerms) {
diff --git a/toolkit/components/satchel/FormHistoryChild.sys.mjs b/toolkit/components/satchel/FormHistoryChild.sys.mjs
index dbc3f9a1f78..52ce9d67717 100644
--- a/toolkit/components/satchel/FormHistoryChild.sys.mjs
+++ b/toolkit/components/satchel/FormHistoryChild.sys.mjs
@@ -7,7 +7,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 const lazy = {};
 
 ChromeUtils.defineESModuleGetters(lazy, {
-  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
+  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
   FormHistoryAutoCompleteResult:
     "resource://gre/modules/FormHistoryAutoComplete.sys.mjs",
   FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs",
@@ -101,25 +101,13 @@ export class FormHistoryChild extends JSWindowActorChild {
         continue;
       }
 
-      // Don't save credit card numbers.
-      if (lazy.CreditCard.isValidNumber(value)) {
-        log("skipping saving a credit card number");
-        continue;
-      }
-
       const name = FormHistoryChild.getInputName(input);
       if (!name) {
         continue;
       }
 
-      if (name == "searchbar-history") {
-        log('addEntry for input name "' + name + '" is denied');
-        continue;
-      }
-
-      // Limit stored data to 200 characters.
-      if (name.length > 200 || value.length > 200) {
-        log("skipping input that has a name/value too large");
+      if (!lazy.FormHistory.isAllowedEntry(name, value)) {
+        log("skipping input that is not eligible to be stored");
         continue;
       }
 
diff --git a/toolkit/components/satchel/FormHistoryParent.sys.mjs b/toolkit/components/satchel/FormHistoryParent.sys.mjs
index 42e5054ae44..54abed8a57b 100644
--- a/toolkit/components/satchel/FormHistoryParent.sys.mjs
+++ b/toolkit/components/satchel/FormHistoryParent.sys.mjs
@@ -43,13 +43,23 @@ export class FormHistoryParent extends JSWindowActorParent {
   }
 
   #onFormSubmitEntries(entries) {
-    const changes = entries.map(entry => ({
-      op: "bump",
-      fieldname: entry.name,
-      value: entry.value,
-    }));
+    // Don't store form history in private browsing sessions, or when the
+    // browsing context cannot be determined.
+    if (this.browsingContext?.usePrivateBrowsing ?? true) {
+      return;
+    }
+
+    const changes = entries
+      .filter(entry => lazy.FormHistory.isAllowedEntry(entry.name, entry.value))
+      .map(entry => ({
+        op: "bump",
+        fieldname: entry.name,
+        value: entry.value,
+      }));
 
-    lazy.FormHistory.update(changes);
+    if (changes.length) {
+      lazy.FormHistory.update(changes);
+    }
   }
 
   get formOrigin() {
@@ -61,6 +71,11 @@ export class FormHistoryParent extends JSWindowActorParent {
   async #onAutoCompleteSearch({ searchString, params, scenarioName }) {
     searchString = searchString.trim().toLowerCase();
 
+    // The search bar manages its own history and is not served here.
+    if (!lazy.FormHistory.isAllowedFieldname(params?.fieldname)) {
+      return { formHistoryEntries: [], externalEntries: [] };
+    }
+
     let formHistoryPromise;
     if (
       FormHistoryParent.canSearchIncrementally(
@@ -99,6 +114,12 @@ export class FormHistoryParent extends JSWindowActorParent {
   }
 
   #onRemoveEntry({ inputName, value, guid }) {
+    // Removals must be scoped to a specific, allowed fieldname so the
+    // fieldname always constrains the query.
+    if (!inputName || !lazy.FormHistory.isAllowedFieldname(inputName)) {
+      return;
+    }
+
     lazy.FormHistory.update({
       op: "remove",
       fieldname: inputName,
Loading diff…