Is BeFrugal: Automatic Coupons and Cash Back safe?

Medium risk

BeFrugal is medium risk. After approving BeFrugal's prompt to manage extensions, it asks its server for IDs to disable, using your stored user token and toolbar version. A returned ID matching an enabled extension gets disabled. No body was captured.

befrugaldevv2026.6.254.0Chrome Web Store
45Risk
Who publishes it

befrugaldev - 2 other listings from the same operator, 2 of them carrying a finding

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

Publisher
befrugaldev

Same store account

2 other listings published from this account, 30k+ users between them. 2 of them carry a finding.

Shared hosts - 5 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.

api.bitlabs.ai
Also called by 2 other listings, including Blockreward - Earn Crypto & Gift Card Rewards
d2z2x9m6jf98op.cloudfront.net
Also called by 2 other listings, including BeFrugal: Automatic Coupons, Highest CashBack
icdn.bfrl.us
Also called by 2 other listings, including BeFrugal: Automatic Coupons, Highest CashBack
partialjs.befrugal.com
Also called by 3 other listings, including BeFrugal: Automatic Coupons, Highest CashBack, Rewards
tbwsjsan.bfrl.us
Also called by 3 other listings, including BeFrugal: Automatic Coupons, Highest CashBack, Rewards

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

Publishers can request a review.

Findings

Server list can turn off other installed extensions

After approving BeFrugal's prompt to manage extensions, it asks its server for IDs to disable, using your stored user token and toolbar version.

A returned ID matching an enabled extension gets disabled.

No body was captured.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You approve BeFrugal's prompt to manage other extensions.

The code requests Chrome's management permission before it checks installed extensions.

The extension did this

The extension asks BeFrugal's server which extension IDs should be turned off.

Enabled installed extensions whose IDs appear in the server response are disabled.

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

No captured request or response body was available for this endpoint; the shipped code shows this POST is the disable-list lookup.

Headers
Content-Type
application/x-www-form-urlencoded
Fields used by the disable-list flow
  • BeFrugal user token
    UserToken form field read from extension storage

    Links the lookup to the BeFrugal profile stored by the extension.

  • Toolbar version
    2026.6.254.0

    Tells the server which installed BeFrugal version is asking for the list.

  • Returned extension IDs
    ExtensionId entries from the server response

    Controls which enabled extensions in your browser are turned off after the match check.

The code that does this

The server disable list feeds the management disable loop

Readable version

API host in deobfuscated source

js/shoptoolbar.js
Site: "www.befrugal.com",APISite: "https://tbwsjs.befrugal.com",PartialSite: "https://partialjs.befrugal.com",CDNSite: "https://tbwsjsan.bfrl.us",

Disable-list POST in deobfuscated source

js/shoptoolbar.js
function _getDisableExtensionsList(a) {  var b = [];  Promise.all([BfrlTool.BrowserStorage.UserToken, shoptoolbar.ToolbarVersionPromise]).then(function(c) {    var d = $jscomp.makeIterator(c);    c = d.next().value;    d = d.next().value;    var f = shoptoolbar.Format("{0}/wssimple.asmx/GetDisableAbleExtensions", shoptoolbar.APISite);    shoptoolbar.FetchJsonPost(f, {      method: "POST",      headers: {        "Content-Type": "application/x-www-form-urlencoded"      },      body: shoptoolbar.Format("UserToken={0}&toolbarversion={1}", c, d)    }).then(function(c) {      if (c)        for (var d = 0; d < c.length; d++) b.includes(c[d].ExtensionId) || b.push(c[d].ExtensionId);      a(b)    })  })}

Installed-extension match and disable loop in deobfuscated source

js/shoptoolbar.js
_GetAllExtensions: function(a) {  browserAdapter.GetActiveTab(!0, function(a) {    browserAdapter._SendTabMessage(a.id, {      action: "focusPermissionPopup",      targetScript: "autoApplyContent.js"    });    var b = "undefined" !== typeof navigator && "undefined" !== typeof navigator.userAgent && navigator && navigator.userAgent && /mac|osx|os x/i.test(navigator.userAgent);    browserAdapter._SendTabMessage(a.id, {      targetScript: "contentscript.js",      action: "addOverlay",      isMac: b    })  });  _requestPermission(function(b) {    browserAdapter.GetActiveTab(!0, function(a) {      browserAdapter._SendTabMessage(a.id, {        targetScript: "contentscript.js",        action: "removeOverlay"      })    });    b && (browserAdapter.GetActiveTab(!0, function(a) {      browserAdapter._SendTabMessage(a.id, {        targetScript: "contentscript.js",        action: "removeQuickTip"      })    }), _getDisableExtensionsList(function(b) {      _getInstalledExtensions(function(c) {        Promise.all([BfrlTool.BrowserStorage.UserToken, shoptoolbar.ToolbarVersionPromise]).then(function(a) {          var b = $jscomp.makeIterator(a);          a = b.next().value;          var d = b.next().value;          b = shoptoolbar.APISite + "/wssimple.asmx/UserExtensionsUpsert";          a = shoptoolbar.Format("extensions={0}&UserToken={1}&toolbarversion={2}", JSON.stringify(c.map(function(a) {            return {              id: a.id,              name: encodeURIComponent(a.name.replace(/[^a-zA-Z0-9]/g, "")),              description: encodeURIComponent(a.description.replace(/[^a-zA-Z0-9]/g, "")),              version: encodeURIComponent(a.version)            }          })), a, d);          shoptoolbar.Post(b, a)        });        var d = [],          e;        for (e in c) {          var f = c[e],            k;          if (k = b.includes(f.id)) k = f.enabled ? !1 : !0, k = !k;          k && d.push(f)        }        if (0 < d.length) {          var l = function(a, b) {            chrome.management.setEnabled(a.id, !1, function() {              b()            })          };          d.reduce(function(a, b) {            return a.then(function() {              return new Promise(function(a) {                l(b, a)              })            })          }, Promise.resolve()).then(function() {            a(d)          })        } else a([])      })    }))  })}
Server involved in the disable-list decision
    • tbwsjs.befrugal.com

    BeFrugal API host that receives the disable-list lookup and returns extension IDs used by the local match check.

Updated 30 September 2026logldmlncddmdfcjaaljjjkajcnacigc