Is BeFrugal: Automatic Coupons, Highest CashBack safe?

High risk

BeFrugal sends your installed extension list to its servers and can disable server-selected extensions.

It uses the browser management permission to read each installed extension's ID, name, description, and version, then posts that list to tbwsjs.befrugal.com. The same flow fetches a server-provided list of extension IDs and disables matching enabled extensions; its checkout detection also sends retailer checkout and order-confirmation URLs with the BeFrugal user token.

BeFrugalv2025.6.247.0Firefox Add-ons
75Risk
Who publishes it

BeFrugal - no other listings under this identity, 4 shared hostnames

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
BeFrugal

Shared hosts - 4 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

d2z2x9m6jf98op.cloudfront.net
Also called by 2 other listings, including BeFrugal
icdn.bfrl.us
Also called by 3 other listings, including BeFrugal, Daily Deals
tbwsjsan.bfrl.us
Also called by 4 other listings, including BeFrugal, Rewards, BeFrugal
partialjs.befrugal.com
Also called by 5 other listings

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Extension inventory is reported before add-ons are disabled

The source shows the disable flow asks for management access, reads your installed extensions, and sends each one's ID, name, description, and version to BeFrugal, then asks which IDs to disable and turns off any enabled match.

Severity
High unwanted
Type
Unexpected
CWE
CWE-506
Source
Dynamic sandbox
What actually happens
You did this

You start the disable-conflicting-extensions flow from the extension's prompt.

The click handler sends the getAllExtensions action to the background script.

The extension did this

The extension reports your installed add-ons to BeFrugal and turns off enabled add-ons selected by BeFrugal's response.

The flow requests management access, reads installed extensions, posts their metadata, fetches a disable list, and disables matching extensions.

Fields handled by the disable-conflicting-extensions flow
  • Installed add-on ID
    aapbdbdomjkkjkaonfhkkikfgjllcleb (illustrative)

    Uniquely identifies each installed extension. Can reveal security tools, shopping extensions, developer tools, or other add-ons you use.

  • Installed add-on name
    Google Translate (illustrative)

    This gives a readable name for each add-on in your browser. It makes the extension inventory understandable without looking up IDs.

  • Installed add-on description
    View translations easily as you browse the web. (illustrative)

    This adds context about what each add-on does. It can expose what kinds of browser tools you rely on.

  • Installed add-on version
    2.0.16 (illustrative)

    This shows which release of each installed add-on you have. It can help distinguish patched and unpatched versions.

  • BeFrugal user token
    bf_k9m4q2_t7xv3a (illustrative)

    Links the inventory request to the extension's BeFrugal account or guest identity, tying it to your browser profile.

Captured request
POSThttps://tbwsjs.befrugal.com/wssimple.asmx/UserExtensionsUpsert

The verification evidence confirms the source constructs this POST in the disable flow; no captured request body was recorded.

Headers
Content-Type
application/x-www-form-urlencoded
The code that does this

Reads installed extensions, posts their metadata, then disables server-selected matches.

Readable version

Readable equivalent of the prompt button

disable/disable_page1.js
disableButton.addEventListener("click", function () {  _sendDisablingMessage("getAllExtensions", null, function (results) {    if (Array.isArray(results) && results.length > 0) {      const encodedResults = encodeURIComponent(JSON.stringify(results));      _sendDisablingMessage("OpenDialog", {        newURL: "disable/disable_page2.html?q=" + encodedResults + "&localparent=" + disableParams["localparent"],        newWidth: 400,        newHeight: 552,        results: results,        isUnknown: false      });      return;    }    _sendDisablingMessage("OpenDialog", {      newWidth: 400,      newHeight: 235,      newURL: "disable/disable_page3.html?localparent=" + disableParams["localparent"],      isUnknown: false    });  });});

Readable equivalent of the background disable logic

js/shoptoolbar.js
function _getDisableExtensionsList(callback) {  const disabledIds = [];  Promise.all([BfrlTool.BrowserStorage.UserToken, shoptoolbar.ToolbarVersionPromise])    .then(function (values) {      const userToken = values[0];      const toolbarVersion = values[1];      const url = shoptoolbar.Format("{0}/wssimple.asmx/GetDisableAbleExtensions", shoptoolbar.APISite);      shoptoolbar.FetchJsonPost(url, {        method: "POST",        headers: { "Content-Type": "application/x-www-form-urlencoded" },        body: shoptoolbar.Format("UserToken={0}&toolbarversion={1}", userToken, toolbarVersion)      }).then(function (response) {        if (response) {          for (let i = 0; i < response.length; i++) {            if (!disabledIds.includes(response[i].ExtensionId)) {              disabledIds.push(response[i].ExtensionId);            }          }        }        callback(disabledIds);      });    });}DisableSystem._GetAllExtensions = function (callback) {  browserAdapter.GetActiveTab(true, function (tab) {    browserAdapter._SendTabMessage(tab.id, { action: "focusPermissionPopup", targetScript: "autoApplyContent.js" });    const isMac = typeof navigator !== "undefined" && /mac|osx|os x/i.test(navigator.userAgent);    browserAdapter._SendTabMessage(tab.id, { targetScript: "contentscript.js", action: "addOverlay", isMac: isMac });  });  _requestPermission(function (granted) {    browserAdapter.GetActiveTab(true, function (tab) {      browserAdapter._SendTabMessage(tab.id, { targetScript: "contentscript.js", action: "removeOverlay" });    });    if (!granted) {      return;    }    browserAdapter.GetActiveTab(true, function (tab) {      browserAdapter._SendTabMessage(tab.id, { targetScript: "contentscript.js", action: "removeQuickTip" });    });    _getDisableExtensionsList(function (serverIds) {      _getInstalledExtensions(function (installedExtensions) {        Promise.all([BfrlTool.BrowserStorage.UserToken, shoptoolbar.ToolbarVersionPromise])          .then(function (values) {            const userToken = values[0];            const toolbarVersion = values[1];            const url = shoptoolbar.APISite + "/wssimple.asmx/UserExtensionsUpsert";            const body = shoptoolbar.Format(              "extensions={0}&UserToken={1}&toolbarversion={2}",              JSON.stringify(installedExtensions.map(function (extension) {                return {                  id: extension.id,                  name: encodeURIComponent(extension.name.replace(/[^a-zA-Z0-9]/g, "")),                  description: encodeURIComponent(extension.description.replace(/[^a-zA-Z0-9]/g, "")),                  version: encodeURIComponent(extension.version)                };              })),              userToken,              toolbarVersion            );            shoptoolbar.Post(url, body);          });        const toDisable = [];        for (const index in installedExtensions) {          const extension = installedExtensions[index];          if (serverIds.includes(extension.id) && extension.enabled) {            toDisable.push(extension);          }        }        if (toDisable.length > 0) {          const disableOne = function (extension, done) {            chrome.management.setEnabled(extension.id, false, function () {              done();            });          };          toDisable.reduce(function (promise, extension) {            return promise.then(function () {              return new Promise(function (resolve) {                disableOne(extension, resolve);              });            });          }, Promise.resolve()).then(function () {            callback(toDisable);          });        } else {          callback([]);        }      });    });  });};
BeFrugal endpoints used by the flow
    • tbwsjs.befrugal.com

    Receives the installed-extension inventory through /wssimple.asmx/UserExtensionsUpsert and returns server-selected extension IDs through /wssimple.asmx/GetDisableAbleExtensions.

What it can do

Permissions this extension asks for, as declared in version 2025.6.247.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    *://*/*

  • See the address and title of every tab you have open

    tabs

  • Watch every request your browser makes

    webRequest

  • Watch, block and rewrite every request your browser makes

    webRequestBlocking

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • Store data in your browser

    storage

  • See, disable and uninstall your other extensions, including your security ones

    management

Where it sends data

Destinations our analysis observed BeFrugal: Automatic Coupons, Highest CashBack contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • tbwsjs.befrugal.com

    BeFrugal: Automatic Coupons, Highest CashBack sends data to tbwsjs.befrugal.com. One other extension we have analysed sends data here.

Updated 30 September 2026amo-1474455