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.

JotMev3.23.0Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI FOUND

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.

01EvidenceCAUSE EFFECT
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.

02EvidenceCODE COMPARE
The code that does this

Unfiltered tab query wired to the open WebSocket

What it actually does
Background handler, formattedservice-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, formattedoverlay/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 */ }))
}
03EvidenceFIELD TABLE
What the WebSocket frame carries
FieldValueWhy it matters
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.
04EvidenceTHIRD PARTY 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.

05EvidenceARTIFACT
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.

RequiresChrome DevToolsAn 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. 1
    Join a Meet call with the extension enabled.
  2. 2
    In DevTools, switch console context to the overlay's frame.
  3. 3
    Paste and run this script.
  4. 4
    Reload the tab and watch the console for the logged frame.
06EvidencePLAIN NOTE
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.

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI FOUND

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.

01EvidenceCAUSE EFFECT
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.

02EvidenceCODE COMPARE
The code that does this

Popup mount call and the background handler that posts the tab list

What it actually does
Popup mount, formattedpopup/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, formattedservice-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}` }
    })
  }))
}
03EvidenceFIELD TABLE
What the POST body carries
FieldValueWhy it matters
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.
04EvidenceTHIRD PARTY LIST
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.

05EvidenceARTIFACT
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.

RequiresChrome DevToolsDeveloper 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. 1
    In chrome://extensions, enable Developer mode, then click the extension's 'service worker' link.
  2. 2
    Paste and run this script there.
  3. 3
    Click the toolbar icon to open the popup.
  4. 4
    Watch the console for the logged POST.
06EvidencePLAIN NOTE
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.

Data recipients

realtime.jotme.ioapi.jotme.io/jotme/popup/metrics
Updated 20 September 2026lpmhkgdhffnlecnomdedaenlhnjembbi