Is Alitools Shopping Assistant safe?

High risk

Alitools is high risk. On install, Alitools reads Google Analytics (_ga, _gid), Yandex Metrica (_ym_uid), and Meta Pixel (_fbp) cookies on alitools.io and sends them in the install payload, confirmed by planting test cookies and seeing them in the request.…

BDT GROUP Sp. z o.o.v26.8.1.18197Chrome Web Store
78Risk
Who publishes it

BDT GROUP Sp. z o.o. - no other listings under this identity, 9 shared hostnames

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

Publisher
BDT GROUP Sp. z o.o.
Registered address
Humańska 8/3, Warszawa, Warszawa 00-789, Poland

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

aliexitem.com
Also called by 3 other listings, including Alitools shopping assistant
alitools.io
Also called by 3 other listings, including Alitools shopping assistant
alitools.link
Also called by 3 other listings, including Alitools shopping assistant
cnf-u45p-alitools.com
Also called by 3 other listings
ext.alitools.io
Also called by 3 other listings
my.alitools.io
Also called by 3 other listings
share.alitools.io
Also called by 3 other listings, including Alitools shopping assistant
assets.allegrostatic.com
Also called by 4 other listings, including Panel Pro dla Allegro Ads
api.alitools.io
Also called by 6 other listings, including AliSaver - AliExpress Shopping and Cashback, Ali Insider- AliExpress Product Research Tool

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

Publishers can request a review.

Findings

Install event sends Google, Yandex, and Facebook tracker cookies to Alitools

On install, Alitools reads Google Analytics (_ga, _gid), Yandex Metrica (_ym_uid), and Meta Pixel (_fbp) cookies on alitools.io and sends them in the install payload, confirmed by planting test cookies and seeing them in the request.

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

You install Alitools Shopping Assistant.

The extension did this

The background worker reads your Google, Yandex, and Facebook tracker cookies and sends their values to Alitools in the install event.

This only happens on the reason='install' path of the onInstalled event, not on updates.

Fields added to the install event payload
  • Google Analytics client ID
    googleClientId: GA1.2.1049581203.1719000000 (illustrative)

    This is the same identifier Google Analytics uses to recognize you across every site that runs it, now also handed to Alitools.

  • Google Analytics session ID
    googleSessionId: 1049581203.1719000000 (illustrative)

    Identifies your current Google Analytics session.

  • Yandex Metrica client ID
    yandexClientId: 1719000000123456789 (illustrative)

    This is the same identifier Yandex Metrica uses to recognize you across sites, now also handed to Alitools.

  • Meta/Facebook Pixel ID
    facebookClientId: fb.1.1719000000000.987654321 (illustrative)

    This is the same identifier Meta's Pixel uses to recognize you across sites that run it, now also handed to Alitools.

The code that does this

Reading the tracker cookies, then attaching them to the install event

Readable version

Cookie getter (renamed)

background.js
async function getCookieValue(name) {  const cookiesUrl = await getConfig("cookiesUrl"); // https://alitools.io  return new Promise((resolve) => {    chrome.cookies.getAll({ url: cookiesUrl, name, path: "/" }, (matches) => {      try {        const first = matches[0];        if (!first) return void resolve(null);        resolve(decodeURIComponent(first.value));      } catch (e) {        resolve(null);      }    });  });}

Install payload assembly (renamed)

background.js
const trackerFields = {  googleClientId: await getCookieValue("_ga"),  googleSessionId: await getCookieValue("_gid"),  yandexClientId: await getCookieValue("_ym_uid"),  facebookClientId: await getCookieValue("_fbp")};const installPayload = {  ...utmSourceFields,  ...trackerFields,  multitools: await getCookieValue("multitools")};sendAnalyticsEvent("install", 4, {  extensionId: await getExtensionId(),  isExtActivated,  ...installPayload});
Captured request
POSThttps://api-dbh1-alitools.com/analytics/events

Observed during dynamic analysis: cookie values planted under the _ga, _gid, _ym_uid, and _fbp names on alitools.io were read back and included verbatim in this POST body, confirming the extension both reads and transmits those cookies rather than only reading them locally.

Body
{  "name": "install",  "version": 4,  "payload": {    "extensionId": "eenflijjbchafephdplkdmeenekabdfb",    "isExtActivated": true,    "googleClientId": "GA1.2.1049581203.1719000000",    "googleSessionId": "1049581203.1719000000",    "yandexClientId": "1719000000123456789",    "facebookClientId": "fb.1.1719000000000.987654321",    "multitools": null  }}
Where the tracker cookie values end up
    • api.alitools.io

    Alitools' analytics backend (AliExpress-focused shopping-assistant vendor). Receives the install event containing your Google, Yandex, and Facebook tracker cookie values.

Observation

This only fires the first time the extension is installed, and only for tracker cookies that are already present in your browser — if you've never visited a site running Google Analytics, Yandex Metrica, or Meta Pixel, the corresponding field is sent as null.

Shopping-site visits sent to Alitools analytics

We observed Alitools posting a navigation event to api-dbh1-alitools.com/analytics/events, naming Amazon's domain and host.

A surfmonitor path can add targetUrl/referrerUrl when enabled remotely; queued locally but never seen leaving.

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

You browse to a shopping site that matches the extension's configuration.

The extension did this

The extension reports the visited domain and host to Alitools analytics.

A separate remote-configuration path can queue the target URL and prior tab URL, but that path was not observed in captured outbound traffic.

Captured request
POSThttps://api-dbh1-alitools.com/analytics/events

204 No Content

Body
{  "name": "stats-task-1",  "version": 2,  "payload": {    "name": null,    "domain": "amazon.com",    "host": "www.amazon.com"  }}
Navigation data handled by the confirmed and gated analytics paths
  • Visited domain
    amazon.com

    Shows which shopping site you visited.

  • Visited host
    www.amazon.com

    Shows the exact host name within that site.

  • Event name
    stats-task-1

    Identifies this as the extension's shopping-site navigation analytics event.

  • Target URL
    https://www.aliexpress.com/ (illustrative)

    Can identify the specific page you opened when the separate remote-configured path is enabled.

  • Referrer URL
    https://www.google.com/ (illustrative)

    Can show which prior page led to the current tab when the separate remote-configured path is enabled.

The code that does this

The background code that builds and sends the navigation events

Readable version

Realtime and batched analytics helpers

background.js
const u = ["aui", "surfmonitor", "screenview", "multitools", "favorites.action"],  l = ["daily-ping", "stats-task-1", "stats-task-4", "promocode-input", "update", "install"];async function T(e) {  if ("chrome" !== _.U1) return !0;  const t = await (0, n.mi)(),    s = await (0, n.gn)(),    r = await (0, n.Se)();  return u.includes(e) ? t && s : l.includes(e) ? t && r : t && s && r}function E(e, t, s) {  if ((0, o.ZB)()) try {    T(e).then((a => {      a && (0, r.jw)({        name: e,        version: t,        payload: s      })    }))  } catch (e) {    console.error(e)  } else(0, r.bN)(r.v6, {    name: e,    version: t,    payload: s  })}async function m(e, t, s, n) {  if ((0, o.ZB)()) try {    n || (n = await async function() {      const e = await (0, i.Qr)(i.do.SERVER_TIME_DIFF);      return (0, c.f5)(new Date, e).toISOString()    }()), T(e).then((async r => {      r && await (0, a.LC)({        name: e,        version: t,        payload: s,        timeCreated: n      })    }))  } catch (e) {    console.error(e)  } else(0, r.bN)(r.i0, {    name: e,    version: t,    payload: s  })}

Endpoint helpers

background.js
function u(e) {  (0, r.L)((0, a.rk)({    url: "/analytics/events",    method: "POST",    json: e  }))}function l(e) {  (0, r.L)((0, a.rk)({    url: "/analytics/events/batch",    method: "POST",    json: e  }))}

Batch queue used by surfmonitor

background.js
async function o() {  return await (0, a.Qr)(a.do.AT_BATCH) || []}async function i(e) {  const t = await (0, r.W)("eventsBatchLimitSize");  let s = [...await o()];  if (s.length >= t) {    const r = s.length - t + 1;    s = s.slice(r).concat(e)  } else s = s.concat(e);  let i = await (0, r.W)("eventsBatchTriggerSize");  try {    await (0, a.Lk)(a.do.AT_BATCH, s)  } catch (e) {    i = s.length  }  s.length >= i && await (0, n.G)()}async function c() {  await (0, a.Lk)(a.do.AT_BATCH, [])}

Remote-config-gated surfmonitor path

background.js
async function E(e) {  if (!await (0, n.W)("statsTask2")) return;  if (!await async function(e) {      const t = await (0, n.W)("statsTask2Urls");      let s = !1;      for (const r of t) {        const t = r.d;        if (e.includes(t)) {          s = !0;          break        }      }      if (s) {        const s = (0, o.QY)(e);        for (const e of t) {          if (s === e.d) return !0        }      }      return !1    }(e.targetUrl)) return;  const t = u(u({}, e), {}, {    domain: (0, o.QY)(e.targetUrl)  });  (0, a.L)((0, i.G)("surfmonitor", 1, t))}

Confirmed stats-task-1 path

background.js
async function c(e) {  if (0 !== e.frameId) return;  await (0, r.W)("statsTask1") && await async function(e, t) {    if (!e.timeStamp || !e.tabId || !e.url) return;    const s = await async function() {      return await (0, i.Qr)(i.do.AV_PREVIOUS_EVENTS) || []    }();    for (const t of s)      if (t.tabId === e.tabId) {        if (t.timeStamp === e.timeStamp) return;        if (t.url === e.url && e.timeStamp - t.timeStamp < 5e3) return;        if (t.tabId === e.tabId) break      } s.unshift(e), s.length > 5 && s.pop();    await async function(e) {      await (0, i.Lk)(i.do.AV_PREVIOUS_EVENTS, e)    }(s), t()  }(e, (async () => {    try {      await async function(e) {        const t = await (0, r.W)("statsTask1Urls"),          s = (0, o.G)(t, e);        if (!s) return;        const i = s.n || null,          c = (0, a._P)(e),          _ = (0, a.QY)(e);        (0, n.ch)("stats-task-1", 2, {          name: i,          domain: _,          host: c        })      }(e.url)    } catch (e) {      console.error(e)    }  }))}
External Alitools hosts involved in this behavior
    • api-dbh1-alitools.com

    Receives the confirmed stats-task-1 analytics POST containing the visited domain and host.

    • api.alitools.io

    Configured as the extension's base API host; the code posts analytics events to /analytics/events and /analytics/events/batch under the API base.

    • cnf-u45p-alitools.com

    Provides remote configuration values such as statsTask1, statsTask1Urls, statsTask2, and statsTask2Urls.

Remote Config Updates Extension Behavior Flags

We observed Alitools request cnf-u45p-alitools.com/config at startup and again in-session.

Successful JSON becomes config, so this remote file can update feature flags, limits, analytics keys, and product URLs without a store update.

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

You install the extension or open a browser profile where it is already installed.

The extension did this

The background script requests a remote configuration file and stores successful responses as extension settings.

Captured request
GEThttps://cnf-u45p-alitools.com/config

Dynamic analysis observed four GET requests: HTTP 200 on startup, HTTP 304 soon after, then another HTTP 200 refresh and HTTP 304 follow-up.

Configuration fields that can be supplied through the remote response
  • Config endpoint
    https://cnf-u45p-alitools.com/config

    Lets a remote service provide settings the extension reads after startup.

  • Refresh interval
    60 minutes

    Controls how often the extension asks for updated settings after it starts.

  • Stats task flags
    statsTask1: false; statsTask2: false; statsTask4: true; statsTask7: false

    Can turn background stats-related tasks on or off for the installed extension.

  • Limiter settings
    gracefulLimiter: false; gracefulLimiterUrls: []

    Can change limiter rules and the URL list those rules apply to.

  • Analytics trace key
    analyticsTraceKey: false

    Can add an analytics-related key that the extension checks before trace behavior runs.

  • Recommended product URLs
    recommendedProductUrls: []

    Can change the URL list used for recommended-product navigation behavior.

When this fires

Every 1 hour

After the startup request, the background script schedules the same configuration refresh using the configured timeout; the shipped default is 60 minutes.

The code that does this

The shipped code hardcodes the config host, stores JSON responses, and schedules refreshes

Readable version

Hardcoded defaults and config storage setter

background.js
        const o = {          apiBaseUrl: "https://api.alitools.io",          cdnBaseUrl: "https://alitools-static.s3-eu-west-1.amazonaws.com/extension/",          landingBaseUrl: "https://ext.alitools.io",          landingPromoUrl: "https://ext.alitools.io",          cabinetBaseUrl: "https://my.alitools.io",          cookiesUrl: "https://alitools.io",          cookiesDomain: ".alitools.io",          shortLinkUrl: "https://alitools.link",          configBaseUrl: "https://cnf-u45p-alitools.com",          configUpdateTimeout: n.Kt * a.EB,          configUpdateTimeoutInMinute: n.Kt,          configEtagCaching: !1,          aeLanguageDomains: ["ar", "cl", "de", "es", "fr", "he", "hz", "id", "it", "ja", "ko", "mx", "nl", "pl", "pt", "ru", "th", "tr", "vi"],          aeAllowedDomains: ["best", "sale", "campaign", "flashdeals", "group", "plaza"],          aeServiceDomains: ["c", "d", "s", "account", "accountHostcommData", "account-info", "account-login", "accounts", "account-settings", "admin", "adservice", "affiliate", "authorize", "bag", "basket", "basketchange", "basket-confirmation", "basketedit", "basketsummary", "bestellen", "billing", "cart", "cashier", "catalog_checkout", "checkout", "checkout1", "checkout-form", "checkouts", "checkouttransfer", "checkout-v1", "checkout-v2", "confirmation", "customer", "customer-account", "feedback", "gcx", "gocheckout", "help", "helppage", "imsg", "internaute", "kasse", "klarnacheckout", "koszyk", "login", "memberaccount", "message", "moje-allegro", "moncompte", "monpanier", "msg", "my", "my.basket", "my_profile", "myaccount", "my-account", "mycart", "onepagecheckout", "one-page-checkout", "onestepcheckout", "order", "orderpipe", "orders", "ordersummary", "passport", "payment", "payments", "portals", "profile", "profiles", "click", "sec", "secure", "shipping-checkout", "shop-cart", "shopping_cart", "shoppingcart", "signin", "thirdparty", "trade", "user", "useraccount", "user-orders", "users", "usuarios", "webaccount", "webapp", "your-account"],          analyticsTraceKey: !1,          extlinkUrl: "https://aliexitem.com/go",          experiments: {},          eventsBatchTriggerSize: 50,          eventsBatchLimitSize: 200,          eventsBatchTimeout: 20 * a.EB,          fetchNotificationsInterval: n.WI * a.EB,          fetchNotificationsIntervalInMinute: n.WI,          manualUpdate: {},          npsSendTimer: 10 * a.EB,          surveySendTimer: 60 * a.EB,          recommendedProductUrls: [],          sessionTimeout: 15 * a.EB,          statsTask1: !1,          statsTask1Urls: [],          statsTask2: !1,          statsTask2Urls: [],          gracefulLimiter: !1,          gracefulLimiterConfig: [],          gracefulLimiterUrls: [],          noticeConfig: [],          statsTask3Domains: {},          statsTask3Timeouts: {            default: 24 * a.kr          },          statsTask3WidgetTimeouts: {            default: 30 * a.EB          },          disableSearchImage: !1,          analyticsExtLinkNavigate: !0,          expExtlinkTimeConfig: {            default: (0, a.LK)(10)          },          extlinkExplorerTimeConfig: {            default: (0, a.an)(3)          },          imageSearchVersion: "v4",          shareEnabled: !0,          shareUrl: "https://share.alitools.io",          webNotifyEnabled: !1,          webNotifyConfig: [],          statsTask4: !0,          analyticsTask1: !1,          statsTask5: {},          statsTask6: (0, a.LK)(30),          statsTask7: !1,          statsTask7ChunksCount: 20,          localStorage: {            compressKeys: [],            logging: {              enable: !1,              chance: 0,              minSizeThreshold: 0,              maxSizeThreshold: 0            }          }        };        async function i(e) {          try {            let t, s;            t = await (0, r.Qr)(r.do.CONFIG), s = await (0, r.Qr)(r.do.DEBUG_CONFIG);            return Object.assign({}, o, t, s)[e]          } catch (t) {            const s = "Error in getConfigItem('".concat(e, "') ").concat(null == t ? void 0 : t.message);            throw console.error(s), new Error(s)          }        }        async function c(e) {          await (0, r.Lk)(r.do.CONFIG, e)        }

Remote config fetch and scheduler

background.js
        async function g() {          try {            const e = {              url: await (0, n.W)("configBaseUrl") + "/config",              isExternal: !0,              options: {}            };            try {              await (0, T.kY)()            } catch (e) {              console.error(e)            }            if (await (0, n.W)("configEtagCaching")) {              const t = await (0, r.Qr)(r.do.CONFIG_ETAG);              t && (e.options.headers = h(h({}, e.options.headers), {}, {                "If-None-Match": t              }))            }            const t = Date.now(),              s = await (0, E.rk)(e),              a = Date.now();            if (!s.ok) return;            if (304 === s.status) return;            const o = s.json;            await (0, n.O)(o);            const i = s.headers.ETag;            i && await (0, r.Lk)(r.do.CONFIG_ETAG, i);            const c = s.headers["X-Server-DateTime"];            c && await async function(e, t, s) {              try {                const a = (0, m.A$)(e, t, s);                await (0, r.Lk)(r.do.SERVER_TIME_DIFF, a)              } catch (e) {                console.error(e)              }            }(t, a, c)          } catch (e) {            console.error(e)          }        }        var p = s(99686);        var f = s(74806);        "chrome" !== _.U1 ? ((0, o.Ts)({          timeout: 4 * a.sh,          step: 100        }, (async () => !!await (0, r.Qr)(r.do.EXT_ID)), (0, i.Y)((async function() {          await (0, c.Lf)(f.qR, f.wy, g), await async function() {            const e = await (0, r.Qr)(r.do.LATEST_CONFIG_LOAD_DATE),              t = (0, p.Z)().format("YYYY-MM-DD");            e && e === t || (await g(), await (0, r.Lk)(r.do.LATEST_CONFIG_LOAD_DATE, t))          }(), (0, u.E)().reloadConfigTest = g        }))), (0, c.Km)(f.qR, f.wy, g)) : ((0, o.Ts)({          timeout: 4 * a.sh,          step: 100        }, (async () => !!await (0, r.Qr)(r.do.EXT_ID)), (0, i.Y)(g)), (async () => {          setInterval(g, await (0, n.W)("configUpdateTimeout"))        })())
Remote host contacted for configuration
    • cnf-u45p-alitools.com

    Receives the extension configuration GET request and returns JSON settings or cache-status responses.

Updated 30 September 2026eenflijjbchafephdplkdmeenekabdfb