Is AI Translator safe?
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.
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
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.
You join a Google Meet call while signed in to the JotMe extension.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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. |
Tab collection and WebSocket send (meetingMetrics)
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 window
chrome.runtime.onMessage.addListener((e, o, t) => {
if (e !== "meetingMetrics") return;
chrome.tabs.query({}).then(tabs => t(tabs.map(tab => ({ href: tab.url }))))
});- realtime.jotme.io
JotMe's own realtime backend. Receives the WebSocket message containing every open tab's URL, authenticated with your access token.
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
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.
You open the JotMe extension's popup, or start a new chat inside it.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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. |
Tab collection and POST on popup open (popupMetrics)
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 UI
Am.send({ chatSessionId, type: "NewChat" }); // starting a new chat
Am.send({ chatSessionId, type: "Open" }); // popup mounted
// service-worker.js message handler
chrome.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}` }
});
});- 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.
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.