Is Batch Upload Tool safe?
Batch Upload Tool sends page URLs, device identifiers, and IP addresses to events.sellfox.com on every product scrape action.
Each time a user runs a scrape on a supported shopping platform, the extension transmits event data to events.sellfox.com via a third-party analytics SDK. The payload includes the current page URL, a session ID, device details (OS name, OS version, device model, language, user agent), and the user's IP address resolved server-side. This telemetry is sent across 20+ platform content scripts using the @meiyunji/analytics-browser SDK initialized with a hardcoded API key.
Who publishes itFairCloud Technology(HK) Limited - 3 other listings from the same operator, none carrying a finding
FairCloud Technology(HK) Limited - 3 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
3 other listings published from this account, 170k+ users between them, none of them carrying 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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Scrape actions send page context to Sellfox analytics
On the confirmed scrape path, clicking the scrape button on a supported marketplace records a crawl analytics event sent to events.sellfox.com, adding the page URL, scrape metadata, and SDK device/session/browser/language/IP context.
What actually happens
You click the scrape button on a supported shopping page.
The Shopee script adds a "Scrape to BigSeller" control and wires its click handler to the crawler callback.
The extension records a crawl analytics event for that page.
The event includes the current page URL, crawl metadata, and SDK-added device and browser context.
Fields the code adds to the analytics event
- Current page URLhttps://shopee.sg/search?keyword=wireless%20mouse (illustrative)
Shows the marketplace page you were viewing when you clicked scrape.
- Crawl event namecrawl
Marks the action as a product-scrape event instead of a general page view.
- Device IDc8f0f6c2-8a35-4bd7-8fc8-2e41022d1b3f (illustrative)
Lets repeated scrape events be linked to the same browser profile.
- Session ID1720798560123 (illustrative)
Groups your scrape activity into the same browsing session.
- Browser and device detailsChrome 126 on Windows, en-US (illustrative)
Adds your browser, operating system, device model, language, and user agent to the event.
- IP-derived network context$remote
Allows the receiving service to associate the event with the network address used for the request.
Captured request
The source code sends analytics payloads to this endpoint with navigator.sendBeacon; no captured request body was available.
The code that does this
Scrape click, crawl event construction, SDK context, and beacon transport
The scrape button click starts the crawler callback
deobfuscated/platform/shopee/index.jsv.onclick = function() { var i = o(u().mark((function i(o) { return u().wrap((function(i) { for (;;) switch (i.prev = i.next) { case 0: return o.stopPropagation(), o.preventDefault(), i.prev = 2, i.next = 5, Kn(c); case 5: if (!Dn(i.sent)) { i.next = 8; break } return i.abrupt("return"); case 8: (p.classList.contains(ei.DETAULT) || p.classList.contains(ei.SUCCESS)) && (p.classList.remove(ei.DETAULT), this.textContent = ii("scraping")), Bn(t) && t({ type: e, href: n, md5key: r }), i.next = 15; break; case 12: i.prev = 12, i.t0 = i.catch(2); case 15: case "end": return i.stop() } }), i, this, [ [2, 12] ]) }))); return function(e) { return i.apply(this, arguments) }}()The crawl analytics event attaches the current page URL and endpoint
deobfuscated/platform/shopee/index.jsfunction qo(e) { var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; try { var n; zo("crawl", { name: e, pageUrl: null === (n = window.location) || void 0 === n ? void 0 : n.href, logData: Jo({}, t) }, { crawlPlatform: t.platform, crawlVersion: "1.2.1" }) } catch (e) {}}function Zo(e, t) { var n = Object.keys(e); if (Object.getOwnPropertySymbols) { var i = Object.getOwnPropertySymbols(e); t && (i = i.filter((function(t) { return Object.getOwnPropertyDescriptor(e, t).enumerable }))), n.push.apply(n, i) } return n}function Wo(e) { for (var t = 1; t < arguments.length; t++) { var n = null != arguments[t] ? arguments[t] : {}; t % 2 ? Zo(Object(n), !0).forEach((function(t) { c(e, t, n[t]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : Zo(Object(n)).forEach((function(t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t)) })) } return e}function Vo(e, t, n) { return t = Ri(t), Ti(e, Xo() ? Reflect.construct(t, n || [], Ri(e).constructor) : t.apply(e, n))}function Xo() { try { var e = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {}))) } catch (e) {} return (Xo = function() { return !!e })()}(0, Go.init)("yosu7anwociqipifcb7at9cy8nxze9ct", "", "", { serverUrl: "https://events.sellfox.com/events"});The SDK context plugin adds device, session, browser, language, and IP fields
deobfuscated/platform/shopee/index.jsYr = function() { function e() { this.name = "context", this.type = Hi.BEFORE, this.eventId = 0, this.library = "amplitude-ts/".concat("2.10.0"), "undefined" != typeof navigator && (this.userAgent = navigator.userAgent), this.uaResult = new(Ur())(this.userAgent).getResult() } return e.prototype.setup = function(e) { return this.config = e, this.eventId = this.config.lastEventId ? this.config.lastEventId + 1 : 0, Promise.resolve(void 0) }, e.prototype.execute = function(e) { return Ni(this, void 0, void 0, (function() { var t, n, i, r, o; return Yi(this, (function(a) { return t = (new Date).getTime(), n = this.uaResult.browser.name, i = this.uaResult.browser.version, r = this.uaResult.device.model || this.uaResult.os.name, o = this.uaResult.device.vendor, this.config.lastEventId = this.eventId, [2, Pi(Pi(Pi(Pi(Pi(Pi(Pi(Pi(Pi(Pi(Pi(Pi({ user_id: this.config.userId, parent_id: this.config.parentId, device_id: this.config.deviceId, session_id: this.config.sessionId, time: t }, this.config.appVersion && { app_version: this.config.appVersion }), this.config.trackingOptions.platform && { platform: "Web" }), this.config.trackingOptions.osName && { os_name: n }), this.config.trackingOptions.osVersion && { os_version: i }), this.config.trackingOptions.deviceManufacturer && { device_manufacturer: o }), this.config.trackingOptions.deviceModel && { device_model: r }), this.config.trackingOptions.language && { language: Nr() }), this.config.trackingOptions.ipAddress && { ip: "$remote" }), { insert_id: fr(), partner_id: this.config.partnerId, plan: this.config.plan }), this.config.ingestionMetadata && { ingestion_metadata: { source_name: this.config.ingestionMetadata.sourceName, source_version: this.config.ingestionMetadata.sourceVersion } }), e), { event_id: this.eventId++, library: this.library, user_agent: this.userAgent })] })) })) }, e}();The transport serializes the payload and sends it with navigator.sendBeacon
deobfuscated/platform/shopee/index.jsqr = function(e) { function t() { return null !== e && e.apply(this, arguments) || this } return Li(t, e), t.prototype.send = function(e, t) { return Ni(this, void 0, void 0, (function() { var n = this; return Yi(this, (function(i) { return [2, new Promise((function(i, r) { var o = _r(); if (!(null == o ? void 0 : o.navigator.sendBeacon)) throw new Error("SendBeaconTransport is not supported"); try { var a = JSON.stringify(t); return i(o.navigator.sendBeacon(e, JSON.stringify(t)) ? n.buildResponse({ code: 200, events_ingested: t.events.length, payload_size_bytes: a.length, server_upload_time: Date.now() }) : n.buildResponse({ code: 500 })) } catch (e) { r(e) } }))] })) })) }, t}(Gr),Zr = function() { return { cookieExpiration: 365, cookieSameSite: "Lax", cookieSecure: !1, cookieStorage: new Fr, cookieUpgrade: !0, disableCookies: !1, domain: "", sessionTimeout: 18e5, trackingOptions: { deviceManufacturer: !0, deviceModel: !0, ipAddress: !0, language: !0, osName: !0, osVersion: !0, platform: !0 }, transportProvider: new Kr }}External analytics destination
- events.sellfox.com
Receives crawl analytics events from the bundled Sellfox analytics SDK, including the page URL and SDK-added device/session context.
Where it sends data
Destinations our analysis observed Batch Upload Tool 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.
- events.sellfox.com
Batch Upload Tool sends data to events.sellfox.com. 3 other extensions we have analyzed send data here.