Is Google Translate Assistant – Side-by-side Translation | PDF Translation safe?

Medium risk

Google Translate Assistant sends page URLs, device identifiers, and tab screenshots to its vendor servers during normal use.

On every user action and daily check-in, the extension transmits a persistent device UUID, user ID, IP country code, and OS fingerprint to translate-useraction-api.xiangtatech.com. When auto-translate is checked for a page, the full URL of that page is sent to translate-api.xiangtatech.com to determine eligibility. If the user invokes the OCR translate feature, a screenshot of the visible tab is captured and uploaded to the same vendor server for processing.

轻主页v1.2.0.3Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

OCR translation uploads selected page images

OCR translation captures the visible tab as a PNG for cropping.

After you confirm the crop, the image and language choices go into multipart form data posted to the picture-translation endpoint.

A runtime test did not trigger this path.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You start screenshot translation and confirm a cropped region of the current page.

The extension did this

The extension captures the visible tab, converts your selected crop into a PNG file, and sends it for picture translation.

02EvidenceFIELD TABLE
Multipart fields prepared for picture translation
FieldValueWhy it matters
Selected page image
image.png from the selected cropSends the visible text and graphics inside the crop you confirm.
Source language
from autoTells the translation service which language the selected image starts from.
Target language
to enTells the translation service which language to translate into.
Page range markers
start_page -1, end_page -1Adds fixed page-range fields even though the upload is a screenshot image.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://translate-api.xiangtatech.com/translate/translation/webs/picture
Source-code proof: the shipped code posts multipart form data containing file, from, to, start_page, and end_page. The runtime test recorded no request to this endpoint.
04EvidenceCODE COMPARE
The code that does this

The screenshot path captures the tab and uploads the cropped image

What it actually does
assets/background.js beautified screenshot caseassets/background.js
                case "screenshot":
                    chrome.tabs.captureVisibleTab(t.tab.windowId, {
                        format: "png"
                    }, (e => n(e)));
                    break;
assets/main6.js beautified screenshot methodsassets/main6.js
            enter() {
                this.captureing || (this.captureing = !0, chrome.runtime.sendMessage({
                    type: "screenshot"
                }, (e => {
                    this.bodyOverflow = document.documentElement.style.overflow, document.documentElement.style.overflow = "hidden", document.addEventListener("keydown", this.onKeydown_), this.imgURL = e, this.isShow = !0
                })))
            },

            confirm() {
                if (!this.cropper) return this.cancel();
                const e = {
                    from: this.sourceItem.gid,
                    to: this.targetItem.gid
                };
                this.cropper.getCroppedCanvas().toBlob((t => {
                    this.cancel(), this.$emit("confirm", t, e)
                }))
            }
assets/main6.js beautified OCR upload methodassets/main6.js
            ocr(e, t) {
                var n, o;
                if (this.loading) return void eo(this.$t("ocr.loading"), {
                    appendToEl: this.$refs.box
                });
                if (!e && !this.ocrBlob) return;
                this.initOriginImg(e || this.ocrBlob), t && (t.from && (null == (n = this.$refs.switchLangRef) || n.setSourceLang(t.from)), t.to && (null == (o = this.$refs.switchLangRef) || o.setTargetLang(t.to))), this.showSceneCoupon = !1, e ? this.ocrBlob = e : e = this.ocrBlob;
                const a = new FormData,
                    i = new File([e], "image.png", {
                        type: e.type
                    });
                a.append("file", i), a.append("from", this.sourceItem.gid), a.append("to", this.targetItem.gid), a.append("start_page", -1), a.append("end_page", -1), ao({
                    url: "https://translate-api.xiangtatech.com/translate/translation/webs/picture",
                    type: "post",
                    formData: a,
                    dataType: "json",
                    success: e => {
                        var t, n;
                        e.data ? (this.clearTransData(), this.loading = !0, this.queryTaskTime(null == (t = e.data) ? void 0 : t.file_translation_id), this.isShow = !0, this.currentTaskId = null == (n = e.data) ? void 0 : n.file_translation_id) : (this.ocrError(e.msg || this.$t("ocr.noText")), this.showErrorDialog = !0)
                    },
                    error: e => {
                        var t, n;
                        1011 !== (null == (t = e.response) ? void 0 : t.code) ? 406 !== (null == (n = e.response) ? void 0 : n.code) ? (this.ocrError(e.response && (e.response.msg || e.response.message) || this.$t("ocr.fail"), e.response && e.response.code || e.status), this.showErrorDialog = !0) : eo(this.$t("ocr.notSupportedFile")) : eo(this.$t("ocr.loading"), {
                            appendToEl: this.$refs.box
                        })
                    }
                })
            },
assets/main6.js beautified OCR entry pointassets/main6.js
            startOcrTranslate(e) {
                return c(this, null, (function*() {
                    if (yield this.closeOpenedFunDialog(), !this.canScreenshot) return;
                    this.inScreenshot = !0, setTimeout((() => {
                        this.$refs.screenshotTool.enter()
                    }), 24);
                    const t = {
                        param15: "default"
                    };
                    e && Object.assign(t, e), this.$mergeSubmitReport({
                        event_id: 107,
                        extend_fields: t
                    })
                }))
            },
            onScreenshotConfirm(e, t) {
                return c(this, null, (function*() {
                    var n;
                    this.inScreenshot = !1, null == (n = this.$refs.imageTranslate) || n.close((() => {
                        var n;
                        null == (n = this.$refs.ocrTranslate) || n.ocr(e, t)
                    }))
                }))
            },
05EvidenceTHIRD PARTY LIST
Picture-translation destination
  • translate-api.xiangtatech.com

    Vendor picture-translation endpoint that receives the cropped screenshot image form.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Persistent browser identifiers sent in action reports

The extension posts a user-action report with a persistent browser-ID prefix, OS values, event IDs, and IP-country context to translate-api.kangaroooffice.com.

It reads stored account/device state, posts reports on install and daily checks.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You browse or use extension features while Google Translate Assistant is active.

The extension did this

The extension builds a user-action report with a persistent browser identifier, device details, event IDs, and account fields when available.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://translate-api.kangaroooffice.com/useraction/api/v2/report
Observed during dynamic analysis: pcid beginning fe13fa8a, os_version 41658296553177090, os_type 1, event_id 1, extend_fields param10=美国; repeated reports used event IDs 100, 101, and 243.
Headers
Content-Typeapplication/json
03EvidenceFIELD TABLE
Fields assembled for the user-action report
FieldValueWhy it matters
Persistent browser identifier
pcid beginning fe13fa8aLets the reporting service connect separate reports from the same browser over time.
Account identifier when signed in
user_id 92837465 (illustrative)Links the report to the extension account if account data is stored locally.
IP-country context
美国Adds the country associated with your network location to the report.
Operating system details
os_type 1, os_version 41658296553177090Adds device context that helps distinguish browsers and environments.
Extension event IDs
event_id 1, 100, 101, or 243Shows which extension action or scheduled check produced the report.
04EvidenceCODE COMPARE
The code that does this

The shipped report builders read storage fields and post the report

What it actually does
assets/browser.js beautified function muassets/browser.js
function mu(e) {
    chrome.storage.local.get(["uuid", "token", "loginRegion", "loginType", "userInfo", "lastRefreshUserInfoTime", "ipCountry", "extShareUnionId", "reportAgree"], (({
        uuid: t,
        token: n,
        loginRegion: a,
        loginType: r,
        userInfo: i,
        lastRefreshUserInfoTime: o,
        ipCountry: s,
        extShareUnionId: l,
        reportAgree: c
    }) => {
        t = (t = t || "").length > 32 ? t.slice(0, 32) : t.padStart(32, "0");
        const _ = f({
                token: n,
                ipCountry: s,
                loginRegion: a
            }),
            d = {
                bs: "bs",
                sub_module: "TranslateAssistant",
                appid: Number("US" === _ ? "132" : "113"),
                unionid: Number(l) || Number("3000") || 1,
                version: gu("1.2.0.2"),
                pcid: t,
                os_version: gu(Eu().version),
                arch: 65535,
                os_type: "mac" === bu() ? 5 : 1,
                events: []
            },
            p = {
                c_timestamp: Math.round(Date.now() / 1e3),
                extend_fields: {
                    scene: 300,
                    param13: "3000"
                }
            };
        if (s && (p.extend_fields.param10 = s), n && (p.extend_fields.param11 = (r || "wx") + "Login", null == i ? void 0 : i.user_id)) {
            if (p.extend_fields.user_id = i.user_id + "", o && i.buy_right_package) {
                const e = new Date - o;
                e > 0 && (i.server_time += Math.round(e / 1e3), i.is_member = i.buy_right_package.some((e => e.unlimited || new Date(e.expire_at).getTime() / 1e3 > i.server_time)))
            }
            p.extend_fields.param1 = i.is_member ? 1 : 0
        }
        e.forEach((([e]) => {
            var t, a;
            "string" != typeof e && "number" != typeof e || (e = {
                event_id: Number(e)
            });
            const r = Ap({}, p);
            129 === e.event_id && n && (null == (a = null == (t = null == i ? void 0 : i.buy_right_package) ? void 0 : t[0]) ? void 0 : a.buy_time) && (r.extend_fields.param12 = i.buy_right_package[0].buy_time), e = Ap(r, e), d.events.push(e)
        })), jp({
            url: "https://translate-useraction-api.xiangtatech.com/api/v2/report",
            type: "post",
            noAppId: !0,
            noToken: !0,
            headers: {
                "Content-Type": "application/json",
                "Sign-Type": 0,
                Business: "TranslateAssistant"
            },
            data: d,
            success() {
                e && e.forEach((([, e]) => {
                    "function" == typeof(null == e ? void 0 : e.success) && e.success()
                })), e = null
            },
            error() {
                e && e.forEach((([, e]) => {
                    "function" == typeof(null == e ? void 0 : e.error) && e.error()
                })), e = null
            }
        })
    }))
}
js/content-start.js beautified function Rpjs/content-start.js
    function Rp(e) {
        chrome.storage.local.get(["uuid", "token", "loginRegion", "loginType", "userInfo", "lastRefreshUserInfoTime", "ipCountry", "extShareUnionId", "reportAgree"], (({
            uuid: t,
            token: n,
            loginRegion: a,
            loginType: r,
            userInfo: i,
            lastRefreshUserInfoTime: o,
            ipCountry: s,
            extShareUnionId: l,
            reportAgree: c
        }) => {
            t = (t = t || "").length > 32 ? t.slice(0, 32) : t.padStart(32, "0");
            const _ = Ep({
                    token: n,
                    ipCountry: s,
                    loginRegion: a
                }),
                d = {
                    bs: "bs",
                    sub_module: "TranslateAssistant",
                    appid: Number("US" === _ ? "132" : "113"),
                    unionid: Number(l) || Number("3000") || 1,
                    version: vp("1.2.0.2"),
                    pcid: t,
                    os_version: vp(Np().version),
                    arch: 65535,
                    os_type: "mac" === yp() ? 5 : 1,
                    events: []
                },
                p = {
                    c_timestamp: Math.round(Date.now() / 1e3),
                    extend_fields: {
                        scene: 300,
                        param13: "3000"
                    }
                };
            if (s && (p.extend_fields.param10 = s), n && (p.extend_fields.param11 = (r || "wx") + "Login", null == i ? void 0 : i.user_id)) {
                if (p.extend_fields.user_id = i.user_id + "", o && i.buy_right_package) {
                    const e = new Date - o;
                    e > 0 && (i.server_time += Math.round(e / 1e3), i.is_member = i.buy_right_package.some((e => e.unlimited || new Date(e.expire_at).getTime() / 1e3 > i.server_time)))
                }
                p.extend_fields.param1 = i.is_member ? 1 : 0
            }
            e.forEach((([e]) => {
                var t, a;
                "string" != typeof e && "number" != typeof e || (e = {
                    event_id: Number(e)
                });
                const r = _p({}, p);
                129 === e.event_id && n && (null == (a = null == (t = null == i ? void 0 : i.buy_right_package) ? void 0 : t[0]) ? void 0 : a.buy_time) && (r.extend_fields.param12 = i.buy_right_package[0].buy_time), e = _p(r, e), d.events.push(e)
            })), Tp({
                url: "https://translate-useraction-api.xiangtatech.com/api/v2/report",
                type: "post",
                noAppId: !0,
                noToken: !0,
                headers: {
                    "Content-Type": "application/json",
                    "Sign-Type": 0,
                    Business: "TranslateAssistant"
                },
                data: d,
                success() {
                    e && e.forEach((([, e]) => {
                        "function" == typeof(null == e ? void 0 : e.success) && e.success()
                    })), e = null
                },
                error() {
                    e && e.forEach((([, e]) => {
                        "function" == typeof(null == e ? void 0 : e.error) && e.error()
                    })), e = null
                }
            })
        }))
    }
05EvidenceTHIRD PARTY LIST
Reporting hosts named by evidence and source code
  • translate-api.kangaroooffice.com

    Observed vendor user-action report destination during dynamic analysis.

  • translate-useraction-api.xiangtatech.com

    User-action report host hardcoded in the shipped reporting functions.

Data recipients

translate-useraction-api.xiangtatech.comtranslate-api.xiangtatech.com
Updated 17 September 2026fjkpgkefceopkogemcjcnncdhbgldolm