Is AI翻訳 safe?
AI翻訳 (JATranslator) sends the URLs of every open browser tab to its own servers during meetings and whenever its popup is opened.
When a Google Meet session starts, the extension opens a WebSocket to its realtime backend and streams the URLs of all open tabs in every browser window, not just the meeting tab, tagged with the current session. Separately, each time the popup is opened it queries the active tab and every other open tab and posts the full list of URLs to the vendor's API, authenticated with the user's stored access token.
Strong link to a flagged operator: 2 of the 3 other listings from the same operator are rated high or critical for malicious behavior. Flagged: JotMe (high), google-meet%E3%81%A7%E3%81%AEai%E7%BF%BB%E8%A8%B3%E3%80%81%E6%96%87%E5%AD%97%E8%B5%B7%E3%81%93%E3%81%97%E3%80%81%E8%AD%B0 (high).
Who publishes itJotme Limited - 3 other listings from the same operator, 3 of them carrying a finding
Jotme Limited - 3 other listings from the same operator, 3 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
3 other listings published from this account, 11k+ users between them. 3 of them carry a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
This extension sends every open tab's URL to its server during Meet calls
Code analysis shows that once you join a Google Meet call, the extension's overlay opens a WebSocket to its own server and sends the URL of every open tab in every browser window, not just the meeting tab.
What actually happens
You join or start a Google Meet call while the extension's translation overlay is active.
The overlay's WebSocket opens, and the extension asks the browser for every open tab across every window, then sends the full URL list over that connection.
This runs automatically once per connection; there is no prompt naming the destination.
The code that does this
Unfiltered tab query wired to the open WebSocket
Background handler, formatted
service-worker.js:141-148chrome.runtime.onMessage.addListener(((e, o, t) => { n(void 0, void 0, void 0, (function*() { if ("meetingMetrics" !== e) return; const o = (yield chrome.tabs.query({})).map((e => e.url)).filter((e => void 0 !== e)).map((e => ({ href: e }))); t(o) }))}))WebSocket onopen, formatted
overlay/react/index.js:24587-24605m4 = function() { WebSocket.OPEN === this.readyState && (chrome.runtime.sendMessage("meetingMetrics", t => { const e = new Date().toISOString(), n = JSON.stringify({ event: "meetingMetrics", timestamp: e }), i = JSON.stringify({ tabs: t, version: "3.23.0", build: "JATranslator" }), o = new Blob([n, "\n", i]); setTimeout(() => { this.send(o) }, 1e4) }), d4 = setInterval(() => { /* ping keepalive, every 20s */ }, 2e4), f4.on("update", t => { /* forwards Yjs doc updates too */ }))}What the WebSocket frame carries
- Every open tab's URLhttps://mail.google.com/mail/u/0/#inbox, https://online.chase.com/dashboard (illustrative)
The web address of each tab open anywhere in the browser, not limited to the Meet tab or its window.
- Event tagevent: "meetingMetrics", timestamp: "2026-09-19T14:02:11.000Z"
The frame is labeled meetingMetrics, tying the tab list to whichever Meet call triggered it.
- Extension version and buildversion: "3.23.0", build: "JATranslator"
The installed extension's version string and internal build name travel with the tab list.
Where the tab list goes
- realtime.jotme.io
The vendor's own realtime backend; receives the full open-tab list once per Meet session over a connection opened with the signed-in user's access token.
Check if you're affected
Hooks WebSocket.prototype.send in the overlay's frame to flag the meetingMetrics frame this claim describes, so you can check for yourself whether it fires.
- Chrome DevTools
- An active Google Meet call with the extension installed
(function () { const OrigWS = window.WebSocket; function Wrapped(...args) { const ws = new OrigWS(...args); const origSend = ws.send.bind(ws); ws.send = function (data) { const inspect = (text) => { if (text.includes("meetingMetrics")) { console.warn("[meet-tab-broadcast-watch] frame sent:", text); } }; if (typeof data === "string") inspect(data); else if (data instanceof Blob) data.text().then(inspect); return origSend(data); }; return ws; } Wrapped.prototype = OrigWS.prototype; window.WebSocket = Wrapped; console.log("[meet-tab-broadcast-watch] Hooked WebSocket. Reload the Meet tab and watch this console.");})();- 1Join a Meet call with the extension enabled.
- 2In DevTools, switch console context to the overlay's frame.
- 3Paste and run this script.
- 4Reload the tab and watch the console for the logged frame.
Observation
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
Opening this extension's popup sends every open tab's URL to its server
Code analysis shows that opening the extension's popup makes it query your active tab plus every other open tab in the window, then POST the full list to the vendor's own backend with your sign-in token attached.
What actually happens
You click the extension's toolbar icon to open its popup.
The popup asks the background script for your active tab's URL and every other open tab's URL, then posts the full list to the vendor's own server with your sign-in token attached.
This runs every time the popup mounts; there is no prompt naming the destination.
The code that does this
Popup mount call and the background handler that posts the tab list
Popup mount, formatted
popup/assets/index-DzwfJWwF.js:22629-22633if (Me.popup) { const mt = Me.popup.sessionId || BT(); bn(mt) /* ... restore saved chat/translate state ... */; Am.send({ chatSessionId: mt, type: "Open" })}Background handler, formatted
service-worker.js:61-96s = e => { const { message: o } = e, t = r(e, ["message"]); n(void 0, void 0, void 0, (function*() { if ("popupMetrics" !== o) return; const n = yield chrome.tabs.query({ active: !0, currentWindow: !0 }), r = (yield chrome.tabs.query({})).filter((e => e?.id !== n?.[0]?.id)); if ("Close" !== t.type) { t.activeTabURL = n?.[0]?.url; t.otherTabs = r.map((e => e?.url)).filter((e => void 0 !== e)).map((e => ({ href: e }))) } const i = (yield chrome.storage.local.get("access")).access; fetch("https://api.jotme.io/jotme/popup/metrics", { method: "POST", body: JSON.stringify(t), headers: { Authorization: `Bearer ${i}` } }) }))}What the POST body carries
- Active tab's URLactiveTabURL: "https://drive.google.com/drive/u/0/folders/1AbC" (illustrative)
The address of whichever tab you have focused when you open the popup.
- Every other open tab's URLotherTabs: [{href:"https://mail.google.com/mail/u/0/"}, {href:"https://online.chase.com/dashboard"}] (illustrative)
The address of every other tab open in that window, sent as a list alongside the active one.
- Sign-in access tokenAuthorization: Bearer eyJhbGciOi... (illustrative)
The same token that authenticates your account is attached to this request as a bearer header.
Where the tab list goes
- api.jotme.io
The vendor's own backend; receives the active tab's URL and every other open tab's URL each time the popup opens, authenticated with the signed-in user's access token.
Check if you're affected
Hooks fetch() inside the extension's service worker to flag the POST this claim describes, so you can check for yourself whether it fires.
- Chrome DevTools
- Developer mode enabled in chrome://extensions
(function () { const target = "api.jotme.io/jotme/popup/metrics"; const origFetch = self.fetch; self.fetch = function (input, init) { const url = typeof input === "string" ? input : input && input.url; if (url && url.includes(target)) { console.warn("[popup-metrics-watch] POST to", url, init && init.body); } return origFetch.apply(this, arguments); }; console.log("[popup-metrics-watch] Hooked fetch() in the service worker. Open the popup and watch this console.");})();- 1In chrome://extensions, enable Developer mode, then click the extension's 'service worker' link.
- 2Paste and run this script there.
- 3Click the toolbar icon to open the popup.
- 4Watch the console for the logged POST.
Observation
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
Where it sends data
Destinations our analysis observed AI翻訳 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.
- realtime.jotme.io
AI翻訳 sends data to realtime.jotme.io. One other extension we have analyzed sends data here.
- api.jotme.io/jotme/popup/metrics
AI翻訳 sends data to api.jotme.io/jotme/popup/metrics. No other extension we have analyzed sends data here.
