Is AiPrice(AliPrice) Sourcing Tool safe?
This extension adds image-based product search, price history, and sourcing tools for major shopping platforms.
It lets users search for similar products from an image, zoom product images, download product images and videos, copy product links and titles, and export store products to Excel. It also shows price and order history on supported marketplaces and adds Amazon listing data such as rankings, ASIN, Amazon seller status, and FBA seller counts.
Who publishes itaiprice2 - 14 other listings from the same operator, 2 of them carrying a finding
aiprice2 - 14 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.
Same store account
14 other listings published from this account, 291k+ users between them. 2 of them carry a finding.
Shared hosts - 1 hostname
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
Screenshot translate sends image data and page URL
Triggering AiPrice(AliPrice)'s screenshot-to-translate captures the tab, crops the selection, and sends an OCR request with the JPEG data, languages, and page URL to api.aiprice.com/chrome/translate/baiduOcrIdentifyWords.
No body recorded.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You trigger screenshot translation from the extension.
The default shortcut is Ctrl+Shift+3 on Windows and Command+Shift+3 on macOS.
The extension captures the visible tab, crops your selected area, and sends it with the page address for OCR.
The request fields are constructed as imgBase64, sourceLang, targetLang, and url.
- Selected screenshot areadata:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxISEhUSEhIVFRUVFRUVFRUVFRUVFRUVFRUWFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMtNygtLisBCgoKDg0OGxAQGzAmICYtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAAEAAQMBIgACEQEDEQH/xAAXAAEBAQEAAAAAAAAAAAAAAAAAAQID/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AM8A/9k= (illustrative)
Contains the visible page content you selected for translation, which can include product text, messages, or other on-screen details.
- Current page URLhttps://detail.1688.com/offer/732418905640.html
Ties the screenshot translation request to the page you were viewing.
- Source languageauto
Shows the language the OCR workflow should read from the screenshot.
- Target languageen
Shows the language selected for the translated OCR result.
The request body was not recorded during traffic capture; the method, URL, and fields are from the shipped request builder.
The shortcut sends the screenshot message to the content script
Readable shortcut command
assets/js/background.js:56207-56223const t = "Ctrl+Shift+3", a = "Command+Shift+3", r = { name: "screenshotToTranslate", command: { suggested_key: { default: t, mac: a }, description: "__MSG_context_menus_translator__" }, handler: { type: "client/translator/screenshot", payload: { trigger: "screenshot_shortcut" } } }Readable screenshot capture route
assets/js/background.js:11325-11341jp = [{ route: "backend/tab/screenshot", handler: function(e, t, a) { return browser.tabs.captureVisibleTab(null, { format: "jpeg", quality: 100, ...e }).then((e => a(e ? (0, I.CW)("success", "", { data: { dataUrl: e } }) : (0, I.CW)("fail", "", { data: {} })))).catch((e => a((0, I.CW)("error", e && e.message ? e.message : "", { err: e })))) }}]The content script crops the JPEG and passes the page URL to OCR
Readable OCR call from the translator flow
assets/js/content-script.js:99365-99391_.indexOf("ocr") > -1 && b.unshift((async (e, t = {}) => { let r = {}; try { if (_.indexOf("screenshot") > -1 && "success" !== t?.status) throw new Error("no screenshot"); if (h(), this.process = "ocr", p = { sl: "", tl: "", ...this.parseLanguageForAPI(a || "", i || "", !0) }, d = { sl: "", tl: "", ...this.parseLanguageForAPI(a || "", i || "", !1) }, y("ocr", [`ocr size ${this.textOrImgDetails.rect.width}x${this.textOrImgDetails.rect.height}`].join("")), r = await (0, ui.p1)(null, { data: { imgBase64: u, url: o, sl: p.sl, tl: p.tl } }), h(), 2001 === r.data.code) throw this.isLogedin = !1, new Error("required login"); if (0 !== r.data.code || !r.data.data) throw new Error(r.data.msg || "ocr fail"); if ("" === (0, ti.stripEmptyLines)((0, ti.stripSpaces)(r.data.data.words))) throw new Error("no words detected"); if (u = r.data.data.words, this.ocr.text = r.data.data.words, this.translation.originalText = r.data.data.words, y("translate", [`source language is ${"auto"===d.sl?d.slAuto.replace(/\(.*\)/,`(${r.data.data.sourceLang})`):d.sl}, `, `target language is ${r.data.data.targetLang}`].join("")), "" === (0, ti.stripEmptyLines)((0, ti.stripSpaces)(r.data.data.translateWords))) throw new Error("no translation"); return this.translation.sl = r.data.data.sourceLang, this.translation.tl = r.data.data.targetLang, this.translation.text = r.data.data.translateWords, e((0, ti.wrapResult)("success", "", {})) } catch (t) { return t.message.indexOf("ocr success") > -1 ? y("ocr", t.message) : y("ocr", "failed"), e((0, ti.wrapResult)("fail", t.message || "", {})) }}))The API wrapper maps image data and URL into the POST request
Readable OCR request builder
assets/js/content-script.js:134006-134033$e = (e, t) => ({ config: { url: `${o.nz}/chrome/translate/baiduOcrIdentifyWords`, method: "POST", params: { ...e.params || {} }, data: { imgBase64: e.data.imgBase64, sourceLang: e.data.sl, targetLang: e.data.tl, url: e.data.url } }, options: t}),De = (e = $e, t = {}, a = {}) => { let i = { config: {}, options: { appendAppInfoToHeader: !0, cache: !1, dataType: "json", ...a || {} } }; return i = e ? e(t, i.options) : $e(t, i.options), i.options.encrypt = y(i.config.data), I(i.config, i.options)}Readable AiPrice API base URL
assets/js/content-script.js:136749-136755let i = "https://www.aiprice.com", r = "https://api.aiprice.com", s = "https://api-cn.aiprice.com", o = "https://agent.aiprice.com", n = "https://index.aiprice.com";const l = i, c = r- api.aiprice.com
Receives the screenshot OCR POST with image data, page URL, and language settings.
Matomo analytics sends page URLs to AliPrice tracking
AiPrice(AliPrice)'s analytics path attaches the page URL to Matomo events.
A POST captured in testing went to matomo.chnprice.com with an event category, action, and encoded URL aiprice.com/resources/operate/doc/video.html?version=4.0.5.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You open or use an extension feature that starts its analytics flow.
The observed request was generated while the extension opened its operating-document page for version 4.0.5.
The extension sends a Matomo event that includes the page address as request context.
The request body included the event category, event action, and the encoded current URL.
Observed during dynamic analysis as a Matomo analytics POST.
e_c=install_view&e_a=open&url=https%3A%2F%2Fwww.aiprice.com%2Fresources%2Foperate%2Fdoc%2Fvideo.html%3Fversion%3D4.0.5- Current page URLhttps://www.aiprice.com/resources/operate/doc/video.html?version=4.0.5
Shows the exact page open when the analytics event was recorded.
- Event categoryinstall_view
Shows which extension workflow the analytics system associated with the page visit.
- Event actionopen
Shows the user-facing event step recorded with that page address.
The analytics code attaches the current page URL before sending the event
Readable Matomo queue sender
assets/js/background.js:9077-9085const kl = async (e = [], t = !1) => { let a = !0; if (a) { let t = globalThis[Al] = globalThis[Al] || []; navigator?.sendBeacon ? (El || (El = !0, t.push(["trackPageView"])), t.push(e)) : !Pl && browser?.runtime?.sendMessage ? browser.runtime.sendMessage({ type: Tl.push2paq, payload: e }) : Pl && "event" === e[0] && Dl.set(e) }}Readable event wrapper
assets/js/background.js:9157-9181Ul = async (e = {}) => { { const { event: t, category: a, action: r, name: o, value: i, details: n, ...s } = { event: "", category: "", action: "", ...e || {} }; let c = "trackEvent", l = a || "", u = r || "", p = o || "", d = i || "", m = n || "", h = []; (0, I.Kg)(l) && (0, I.Kg)(u) && (kl(["setCustomDimension", 6, globalThis.location.href, "action"]), (0, I.Im)(m) || null === m || void 0 === m || kl(["setCustomDimension", 7, JSON.stringify(m), "action"]), h.push(l, u), (0, I.Kg)(p) && (h.push(p), (0, I.Et)(d) && h.push(d)), kl([c].concat(h))) }}Readable privacy-status gate
assets/js/background.js:9182-9191Ml = async (e, t, a) => { try { const r = await R.F.get("settings"); if (r?.data?.privacyStatus && (Rl(e) && zl((() => Ol(e)), "[sendToAlipriceAnalyze] Error"), Nl(t) && zl((() => Ll(t)), "[sendToAlipriceStatistics] Error"), (0, I.Qd)(a))) { let e = !1; "labelAsUrl" in a && (e = a.labelAsUrl, delete a.labelAsUrl), zl((() => Fl(a, { labelAsUrl: e })), "[sendToGoogle] Error") } } catch (e) {}}Default settings keep the analytics privacy switch enabled
Readable settings migration
assets/js/background.js:13325-13329r = async e => { let t = (await R.F.get("settings")).data || {}, r = {}; "platforms" in t || (t = (0, I.qt)(a.defaultSettings || {})), r = (0, I.h1)({}, (0, I.qt)(a.defaultSettings || {}), (0, I.qt)(t || {})), (0, I.Tn)(a.migrateSettings) && (r = a.migrateSettings((0, I.qt)(r), (0, I.qt)(t || {}), (0, I.qt)(a.defaultSettings || {}))), "privacyStatus" in r && (r.privacyStatus = !0), zd({ installedInfo: e, memoryCheck: a.memoryCheck, settings: r })}Readable default setting
assets/js/background.js:30331-30335Wv = () => ({ privacyConsentRequired: !1, privacyStatus: !0, privacyItemCookiesStatus: !0, privacyItemFunctionalStatus: !0})- matomo.chnprice.com
Receives the Matomo event POST containing the page URL and event fields.
What it can do
Permissions this extension asks for, as declared in version 4.1.6. 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
http://*/*
Read and change your data on every secure site you visit
https://*/*
Act on the current tab, but only after you click the extension
activeTab
Schedule its own background tasks
alarms
Add items to the right-click menu
contextMenus
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
Show you desktop notifications
notifications
Run its own code inside the pages you visit
scripting
Store data in your browser
storage