Is AI Translator safe?

Medium risk

AI Translator sends the URLs of every open browser tab, across all windows, to JotMe's servers on popup open or Google Meet calls.

When the user opens the extension's popup or joins a Google Meet call while signed in, AI Translator queries the full list of open tabs across every browser window (not just the active one) and transmits their URLs to JotMe's backend. On Google Meet calls this goes over an authenticated WebSocket to realtime.jotme.io; from the popup it is sent via an HTTPS POST to api.jotme.io, in both cases carrying the signed-in user's 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

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

Joining a Google Meet call sends the URLs of every open tab to JotMe

HighunwantedCWE-359Static analysis

Code analysis shows that once you join a Google Meet call while signed in to JotMe, the extension opens a WebSocket to JotMe's server and sends the URLs of every open tab across all your browser windows, not just Meet or JotMe tabs.

What actually happens

You did this

You join a Google Meet call while signed in to the JotMe extension.

The extension did this

The extension collects the URL of every open browser tab, in every window, and sends the list to JotMe's server over a WebSocket.

The send happens automatically about 10 seconds after the WebSocket connects, with no prompt.

Fields in the meetingMetrics WebSocket message

  • URL of every open tab
    https://mail.google.com/mail/u/0/#inbox

    Covers tabs on any site, in any window, open at the time of the call; not limited to Google Meet or jotme.io.

  • Your access token
    eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzQ0MTIifQ.abc123signature

    Included in the WebSocket connection itself, so the server can tie the tab list to your signed-in JotMe account.

  • Extension build
    3.23.0 / ENTranslator

    Identifies which version of the extension collected the data.

The code that does this

Tab collection and WebSocket send (meetingMetrics)

Readable version
const p4 = (t, e) => function() {  const n = new CustomEvent("displayMultipleWSWarning");  window.dispatchEvent(n), clearInterval(d4), setTimeout(() => {    const r = encodeURIComponent(window.location.pathname);    if (!e.access) return;    const i = new WebSocket("wss://realtime.jotme.io", [e.access, r]);    i.onopen = m4, i.onmessage = g4, i.onerror = b4, i.onclose = p4(t, e), t(i)  }, 1e4)};const m4 = function() {  WebSocket.OPEN === this.readyState && chrome.runtime.sendMessage("meetingMetrics", t => {    const e = new Date().toISOString();    const n = JSON.stringify({ event: "meetingMetrics", timestamp: e });    const i = JSON.stringify({ tabs: t, version: "3.23.0", build: "ENTranslator" });    const o = new Blob([n, "\n", i]);    setTimeout(() => { this.send(o) }, 1e4)  })};// service-worker.js: answers the 'meetingMetrics' message with every open tab, any windowchrome.runtime.onMessage.addListener((e, o, t) => {  if (e !== "meetingMetrics") return;  chrome.tabs.query({}).then(tabs => t(tabs.map(tab => ({ href: tab.url }))))});

Where the tab list is sent

    • realtime.jotme.io

    JotMe's own realtime backend. Receives the WebSocket message containing every open tab's URL, authenticated with your access token.

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 JotMe's popup sends the URLs of every other open tab to its server

HighunwantedCWE-359Static analysis

Code analysis shows that opening JotMe's toolbar popup, or starting a new chat, makes the extension query every open tab across all windows and POST the active tab's URL plus every other tab's URL to JotMe's server.

What actually happens

You did this

You open the JotMe extension's popup, or start a new chat inside it.

The extension did this

The extension collects the active tab's URL plus the URLs of every other open tab in every window, and POSTs the list to JotMe's server.

This happens on every popup open and every new chat, not just once per session.

Fields in the popupMetrics POST body

  • Active tab's URL
    https://docs.google.com/document/d/1AbCkX/edit

    The page you were looking at when you opened the popup.

  • URLs of your other open tabs
    [{"href":"https://mail.google.com/mail/u/0/#inbox"},{"href":"https://github.com/acme/internal-repo"}]

    Every other tab open in any browser window at that moment, on any site.

  • Your access token
    Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzQ0MTIifQ.abc123signature

    Sent as an Authorization header so the server can tie this tab list to your signed-in account.

The code that does this

Tab collection and POST on popup open (popupMetrics)

Readable version
class Am {}Am.send = (o) => {  const parsed = schema.safeParse(o);  if (!parsed.success) return;  chrome.runtime.sendMessage({ message: "popupMetrics", ...parsed.data });};// call sites in the popup UIAm.send({ chatSessionId, type: "NewChat" });   // starting a new chatAm.send({ chatSessionId, type: "Open" });      // popup mounted// service-worker.js message handlerchrome.runtime.onMessage.addListener(async (msg) => {  if (msg.message !== "popupMetrics") return;  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });  const otherTabs = (await chrome.tabs.query({}))    .filter(t => t.id !== activeTab?.id);  if (msg.type !== "Close") {    msg.activeTabURL = activeTab?.url;    msg.otherTabs = otherTabs.map(t => ({ href: t.url }));  }  const { access } = await chrome.storage.local.get("access");  await fetch("https://api.jotme.io/jotme/popup/metrics", {    method: "POST",    body: JSON.stringify(msg),    headers: { Authorization: `Bearer ${access}` }  });});

Where the tab list is sent

    • api.jotme.io

    JotMe's own API. Receives the active tab URL and every other open tab's URL on every popup open, authenticated with your access token.

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 Translator 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 Translator sends data to realtime.jotme.io. One other extension we have analyzed sends data here.

  • api.jotme.io

    AI Translator sends data to api.jotme.io. No other extension we have analyzed sends data here.

Updated 15 October 2026hhappcbkglbedakgldofnaepeaaijolk