Is Alitools Shopping Assistant safe?
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.…
Who publishes itBDT GROUP Sp. z o.o. - no other listings under this identity, 9 shared hostnames
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.
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.
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
You install Alitools Shopping Assistant.
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.
- Google Analytics client IDgoogleClientId: 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 IDgoogleSessionId: 1049581203.1719000000 (illustrative)
Identifies your current Google Analytics session.
- Yandex Metrica client IDyandexClientId: 1719000000123456789 (illustrative)
This is the same identifier Yandex Metrica uses to recognize you across sites, now also handed to Alitools.
- Meta/Facebook Pixel IDfacebookClientId: 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.
Reading the tracker cookies, then attaching them to the install event
Cookie getter (renamed)
background.jsasync 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.jsconst 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});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.
{ "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 }}- api.alitools.io
Alitools' analytics backend (AliExpress-focused shopping-assistant vendor). Receives the install event containing your Google, Yandex, and Facebook tracker cookie values.
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
You browse to a shopping site that matches the extension's configuration.
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.
204 No Content
{ "name": "stats-task-1", "version": 2, "payload": { "name": null, "domain": "amazon.com", "host": "www.amazon.com" }}- Visited domainamazon.com
Shows which shopping site you visited.
- Visited hostwww.amazon.com
Shows the exact host name within that site.
- Event namestats-task-1
Identifies this as the extension's shopping-site navigation analytics event.
- Target URLhttps://www.aliexpress.com/ (illustrative)
Can identify the specific page you opened when the separate remote-configured path is enabled.
- Referrer URLhttps://www.google.com/ (illustrative)
Can show which prior page led to the current tab when the separate remote-configured path is enabled.
The background code that builds and sends the navigation events
Realtime and batched analytics helpers
background.jsconst 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.jsfunction 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.jsasync 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.jsasync 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.jsasync 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) } }))}- 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
You install the extension or open a browser profile where it is already installed.
The background script requests a remote configuration file and stores successful responses as extension settings.
Dynamic analysis observed four GET requests: HTTP 200 on startup, HTTP 304 soon after, then another HTTP 200 refresh and HTTP 304 follow-up.
- Config endpointhttps://cnf-u45p-alitools.com/config
Lets a remote service provide settings the extension reads after startup.
- Refresh interval60 minutes
Controls how often the extension asks for updated settings after it starts.
- Stats task flagsstatsTask1: false; statsTask2: false; statsTask4: true; statsTask7: false
Can turn background stats-related tasks on or off for the installed extension.
- Limiter settingsgracefulLimiter: false; gracefulLimiterUrls: []
Can change limiter rules and the URL list those rules apply to.
- Analytics trace keyanalyticsTraceKey: false
Can add an analytics-related key that the extension checks before trace behavior runs.
- Recommended product URLsrecommendedProductUrls: []
Can change the URL list used for recommended-product navigation behavior.
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 shipped code hardcodes the config host, stores JSON responses, and schedules refreshes
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")) })())- cnf-u45p-alitools.com
Receives the extension configuration GET request and returns JSON settings or cache-status responses.