Is XTranslate safe?
XTranslate sends translated text and a persistent installation UUID to its own servers and usage events to Google Analytics.
Every translation request — including the text being translated — is transmitted to xtranslate.dev, where it is linked to a per-installation anonymous identifier stored in synced browser storage. Authenticated users' requests include session cookies via credentials:include, associating translated content with their account. Separately, the extension reports translation provider, language pairs, and user actions to Google Analytics using the same persistent UUID.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Free-trial translations post text with a persistent UUID
XTranslate's free-trial flow posts text to xtranslate.dev with a persistent anonymous ID from sync storage.
A traffic capture missed this endpoint, but the source wires the trial button to it and updates the local quota from the response.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You use the free-trial AI translation option.
The UI path is the popup's free-trial action button, which calls the trial translation method.
The extension code builds a POST request containing the text to translate and the stored anonymous ID.
The request is sent to xtranslate.dev and the returned quota values update the extension's stored trial state.
The endpoint and method are from the shipped source code; a separate traffic observation saw xtranslate.dev pricing and user-plan requests but did not capture this demo request.
- Content-Type
- application/json
- Anonymous trial IDf47ac10b-58cc-4372-a567-0e02b2c3d479
This lets the service connect free-trial translation requests to the same extension installation.
- Text to translateQuarterly roadmap review starts at 14:00
This is the selected page text or entered text submitted for translation.
- Source languageauto
This records the language the extension is translating from.
- Target languageen
This records the language you asked the extension to translate into.
Free-trial state, button path, and POST body
Readable background bundle: trial state
background.jsconst ti = ys("xtranslate_pro_trial", { area: "sync", autoLoad: !0, saveDefaultWhenEmpty: !0, defaultValue: { anonId: crypto.randomUUID?.() ?? Math.random().toString(36).substring(2), todayRemain: 2, totalRemain: 5, finished: !1, showBanner: !0 }});Readable background bundle: trial POST
background.jsasync translateTrial(e) { const t = ti.get(), n = this.request({ url: `${this.apiUrl}/translate/demo`, requestInit: { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ anon_id: t.anonId, langFrom: e.from, langTo: e.to, text: e.texts ?? [e.text] }) } }); try { const { limits: r, result: s } = await n, { remaining_today: i, remaining_total: a } = r; return t.todayRemain = i, t.totalRemain = a, t.finished = 0 === a, i || $s("promo_free_ai_translation_limit_daily", {}), a || $s("promo_free_ai_translation_limit_total", {}), this.toTranslationResult(s, e) } catch (e) { this.handleFreeApiError(e) } finally { $s("promo_free_ai_translation_used", {}) }}Readable content script: button calls trial translation
content-script-injectable.jsasync translateWithFreeTrial() { this.lastParams.provider = T.Qk.XTRANSLATE_PRO; const e = this.getPayloadParams(this.lastParams); await this.translate(e, (() => (0, T.q4)().translateTrial(e))), this.isFreeTrialUsed = !0}- www.xtranslate.dev
Receives the free-trial demo translation request with the anonymous trial ID, language settings, and text array.
Usage telemetry posts to Google Analytics with a persistent ID
XTranslate posts usage telemetry to Google Analytics after a screen view, tying it to a persistent GA client ID in sync storage, with parameters like platform and screen name.
The same sender also handles translation, history, TTS events.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You open or use a translation-related feature in the extension.
The observed event was a screen view for the translate screen; the same code also sends translation, history, text-to-speech, and promotion events.
The extension posts a Google Analytics event tied to the same stored installation ID.
The event includes a platform label and feature-specific parameters, such as screen name or language-pair details.
Observed during dynamic analysis: the request body contained a client_id beginning with a120cc5d and events[{name:screen_view, params:{platform:extension, screen_name:translate}}].
- Content-Type
- application/json
- Persistent installation IDclient_id beginning with a120cc5d
This lets multiple extension events be associated with the same browser installation.
- Extension platformextension
This marks the event as coming from the browser extension rather than another app or website.
- Screen viewedtranslate
This records which extension screen you opened.
- Translation event detailstranslate_used with provider google, lang_from auto, lang_to en
For translation events, this describes the provider and language pair used for the request.
Stored GA client ID and Measurement Protocol sender
Readable background bundle
background.jsconst Ls = ys("ga_client_id", { area: "sync", defaultValue: crypto.randomUUID?.() ?? Math.random().toString(36).substring(2), saveDefaultWhenEmpty: !0}),$s = Rr({ messageType: pr.GA_METRICS_SEND_EVENT, handler: async function(e, t) { try { await Ls.load(); const n = "https://www.google-analytics.com/mp/collect?measurement_id=G-HKWWGL29S8&api_secret=<redacted>", r = { client_id: Ls.get(), events: [{ name: e, params: { platform: "extension", ...t } }] }; return await fetch(n, { method: "POST", body: JSON.stringify(r), headers: { "Content-Type": "application/json" } }) } catch (n) { console.warn(`GA event "${e}" failed to send: ${n}`, t) } }});Screen-view event source
app.jsstatic bindPageIdWatcher() { return (0, f.reaction)((() => mr().page), (e => { jt("screen_view", { screen_name: e }) }), { fireImmediately: !0 })}Translation event source
app.jsasync translate(e = { ...this.params}) { if (this.summarized = "", e.text && !se()(this.lastParams, e)) { this.input?.focus(), this.input?.setValue(e.text), this.translation = null, this.error = null, this.isLoading = !0; try { this.lastParams = e; const t = await Kt(e.provider).translate({ from: e.from, to: e.to, text: e.text.trim(), mode: "lookup" }); se()(e, this.params) && (this.translation = t, jt("translate_used", { source: "translate_tab", provider: e.provider, lang_from: e.from, lang_to: e.to })) } catch (t) { se()(e, this.params) && (this.error = t, jt("translate_error", { error: String(this.error?.message ?? t), source: "translate_tab", provider: e.provider, lang_from: e.from, lang_to: e.to })) } finally { this.isLoading = !1, this.updateUrl(e) } }}- www.google-analytics.com
Receives Measurement Protocol events containing the persistent client ID and extension usage parameters.
Where it sends data
Destinations our analysis observed XTranslate 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.
- www.google-analytics.comwidely used
XTranslate sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.
- www.xtranslate.dev
XTranslate sends data to www.xtranslate.dev. No other extension we have analysed sends data here.