Is AI翻訳 safe?

Medium risk

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).

JotMev3.23.0Chrome Web Store
55Risk
Who publishes it

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.

Publisher
JotMe
Declared legal entity
Jotme Limited
Registered address
124 City Road, London EC1V 2NX, GB
Registered contact
JOTME LIMITED

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

HighunwantedCWE-359Static analysis

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 did this

You join or start a Google Meet call while the extension's translation overlay is active.

The extension did this

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

Readable version

Background handler, formatted

service-worker.js:141-148
chrome.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-24605
m4 = 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 URL
    https://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 tag
    event: "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 build
    version: "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.

Requires
  • Chrome DevTools
  • An active Google Meet call with the extension installed
meet-tab-broadcast-watch.js · js
(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.");})();
How to run it
  1. 1Join a Meet call with the extension enabled.
  2. 2In DevTools, switch console context to the overlay's frame.
  3. 3Paste and run this script.
  4. 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

HighunwantedCWE-359Static analysis

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 did this

You click the extension's toolbar icon to open its popup.

The extension did this

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

Readable version

Popup mount, formatted

popup/assets/index-DzwfJWwF.js:22629-22633
if (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-96
s = 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 URL
    activeTabURL: "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 URL
    otherTabs: [{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 token
    Authorization: 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.

Requires
  • Chrome DevTools
  • Developer mode enabled in chrome://extensions
popup-metrics-watch.js · js
(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.");})();
How to run it
  1. 1In chrome://extensions, enable Developer mode, then click the extension's 'service worker' link.
  2. 2Paste and run this script there.
  3. 3Click the toolbar icon to open the popup.
  4. 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.

Updated 22 October 2026lpmhkgdhffnlecnomdedaenlhnjembbi