Is Sidebar: ChatGPT, Bookmarks, GPT-4o | Meomni safe?
Meomni Sidebar sends full page text, selected text, and Gmail thread content to its backend server on demand.
When users interact with the AI assistant, the extension reads the active tab's full page text (title, URL, and extracted body content) and any selected text, then transmits this data to v2-mlqv2syt5a-uc.a.run.app as AI prompt context. The extension also scrapes Gmail inbox metadata via Atom feed and extracts full Gmail conversation threads — including message bodies, sender emails, and attachment names — which are included in chat requests to the same backend. Additionally, it strips Content-Security-Policy and X-Frame-Options headers from all sub-frame requests across every site the user visits.
Who publishes itRendom - 8 other listings from the same operator, none carrying a finding
Rendom - 8 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
8 other listings published from this account, 18k+ users between them, none of them carrying a finding.
Shared hosts - 2 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Active Page Text Sent as AI Context
Code analysis shows using the extension's Web AI actions collects the active tab's URL, page title, and page text, posted to the Meomni AI backend.
Traffic testing reached the sign-in wall; no live /api/v2/ai/chat body was captured.
What actually happens
You trigger a Web AI action in the extension.
The overlay code configures Web actions with active-page context before calling the AI workflow.
The extension reads the current page and prepares it for an AI chat request.
The service worker asks the content script for page HTML, extracts readable text, and carries the URL and title into the prompt context.
Fields assembled for the AI context
- Current page URLhttps://company.example/wiki/2026-product-plan (illustrative)
Identifies the exact page you asked the extension to summarize or process.
- Page title2026 Product Plan - Internal Wiki (illustrative)
Adds a readable label for the page you are viewing.
- Extracted page textLaunch goals, customer notes, and draft timeline for Q3 planning (illustrative)
Can include the article, document, ticket, or form text visible in the active page.
Captured request
Traffic testing reached a sign-in wall before the AI chat path ran; no request body was captured.
- x-app-id
- fnfdomooadjpfohbepiaonnbdmkdjiog
- x-app-version
- 5.0.27
The code that does this
Shipped code path from Web AI action to page-text POST
Web actions attach active-tab context before calling the AI workflow
assets/useOverlay-EAeRTiwI.jsh = a(() => { if (!r.value?.startsWith("https")) return null; let t = { author: "system", title: "Web", templates: ["summarize", "tldr"], userContext: [{ type: "active-tab-content", value: "" }] }; const e = l(o.activeTab?.url, y.value); return e && (t = e), t.templates = t.templates.map(t => ({ id: t, ...o.ai_getTemplate(t).meta || {} })), t});return { isActionLoading: i, pageAction: async function(t, e) { try { if (i.value = t, !o.isTruePlus) return void o.dispatch("actionOpenGetPlusModal"); o.dispatch("frontAction_navigate", { routePreset: "gpt" }); await o.dispatch("actionCallLlm", { templateId: t, ...e?.userContext && { userContext: e.userContext }, save: !0, options: { activeModel: "advanced" } }) } catch (a) {} finally { i.value = null } }, activeHostRule: h, overlayAiTemplates: v, overlayApps: m, allApps: p, allAiTemplates: d, updateOverlay: function(t) { o.dispatch("commitAndSave", { commit: "overlay$merge", payload: t, to: "overlay" }) }}The content script returns the page HTML
contentScript/contentScript.jscase "getRawHtml": return Promise.resolve(document.documentElement.outerHTML);The service worker parses the active tab and returns title, URL, and textContent
assets/background.js--ICHH-ml.jsactionGetActiveTabReader: async (t, e) => { const n = await l(); let r = { url: n?.url, textContent: "", title: n?.title || "", excerpt: "", length: 0, lang: "", content: "" }; if (!n?.id || !n?.url || !n.url.startsWith("http")) return r; const i = await u(n.id, { to: "contentScript", name: "getRawHtml" }); if (!i) return r; const a = await t.dispatch("offScreenAction", { name: "readability-parse", data: { html: i, url: n.url } }).catch(t => null); if (!a) return r; let s = a.textContent || ""; return s && (s = lt(s)), { ...a, url: n.url, textContent: s, title: a.title || n.title || "", excerpt: a.excerpt || "", length: a.length || 0, lang: a.lang || "", content: a.content || "" }}ACTIVE_TAB_CONTENT is copied into the prompt context fields
assets/background.js--ICHH-ml.jsgetVariableResult: async (t, e) => { const { type: n, value: r } = e; let i = { type: n, content: r }; switch (n) { case it.TemplateTypes.VariableType.SELECTED_TEXT: { const e = await t.selectedText.value || ""; i.content = e; break } case it.TemplateTypes.VariableType.ACTIVE_TAB_CONTENT: { const e = await t.dispatch("actionGetActiveTabReader"); if (!e?.textContent) throw new Error("Web page text content is not available"); e?.title && (i.title = e.title), e?.url && (i.url = e.url), e?.textContent && (i.content = e?.textContent); break } case it.TemplateTypes.VariableType.YOUTUBE_CAPTIONS: { const e = await t.dispatch("actionYoutubeGetCaptions"); i.content = JSON.stringify(e[r || "text"]), i.url = t.activeTab.url || "", i.title = t.activeTab.title || ""; break } case it.TemplateTypes.VariableType.DIALOG: i.content = r || await t.dispatch("scraperGetConversation"); break; case it.TemplateTypes.VariableType.FILE_DOCUMENT: i.content = r ? await t.dispatch("frontAction_urlToText", r) : "", e?.filename && (i.filename = e.filename) } return i}The AI client posts the assembled data to ai/chat
assets/background.js--ICHH-ml.jsapiClient: { call: async (e, n) => { try { let i; return i = t.ai_useApiKey ? await t.dispatch("openaiApiDirect", { ...e, options: { ...e.options, activeModel: (r = e.options?.activeModel, { standard: "gpt-4o-mini", advanced: "gpt-4o", pro: "gpt-5" } [r] || "gpt-4o-mini") } }) : await t.dispatch("meomniApi", { route: n ? "ai/chat/stream" : "ai/chat", method: "POST", data: e, onStream: n }), i?.choices || i?.usage ? et.extractDataFromResponse(i) : i } catch (i) { throw i } var r }}The vendor backend base URL used by meomniApi
assets/mainConfig-D0q9gJWx.jsimport { b as e} from "./object-T1iCcc7Q.js";const t = "https://v2-mlqv2syt5a-uc.a.run.app/api/v2", a = { microsoft: { redirectUrl: `${t}/oauth/microsoft/redirect`, clientId: "b6906661-2c9c-428e-8323-bd0e81ebf016" }, ticktick: { redirectUrl: "https://us-central1-taberium-backend.cloudfunctions.net/main/api/oauth-redirect-ticktick", clientId: "FXuG8s0c7CKyMx2KCx" }, todoist: { redirectUrl: "https://v2-mlqv2syt5a-uc.a.run.app/api/v2/oauth/todoist/redirect", clientId: "3149ceccf5d944379621cb77d9f7e6e1" } }Destination host
- v2-mlqv2syt5a-uc.a.run.app
Meomni backend that receives the AI chat request assembled by the extension code.
Gmail thread contents sent to AI chat endpoint
When the Gmail feature runs, it reads the visible conversation, extracts sender, text, timestamps, and attachment names, then sends that context to the AI endpoint.
DA confirmed the code runs; it didn't capture the request body.
What actually happens
You use the extension's Gmail response feature while viewing a Gmail conversation.
The extension reads the visible Gmail thread and sends the parsed conversation as AI chat context.
Gmail fields parsed from the page before the AI request
- Sender name and emailMaya Patel <maya.patel@example.com> (illustrative)
This can identify who is participating in the email thread you are viewing.
- Message body textHi Maya, the Q3 launch deck is attached. Can you review before 3 PM? (illustrative)
This can include the private contents of the email messages visible in the thread.
- Message timestampJul 12, 2026, 10:42 AM (illustrative)
This shows when messages in the thread were sent or received.
- Attachment namesQ3-launch-plan.pdf (illustrative)
File names can reveal projects, customers, or subjects discussed in the thread.
Captured request
The code that does this
The shipped code gates on Gmail, scrapes the thread, and sends it to AI chat
Gmail side-panel trigger and AI context call
assets/AiRespond-DJhiZIU6.jsconst xe = a(() => ["mail.google.com"].some(e => oe.value?.includes(e)));async function fe() { try { ne.value = !0; const e = await Y.dispatch("scraperGetConversation"); if (ie.value = e.conversation, ie.value) { const e = ie.value[ie.value.length - 1]; X.value = e.content; const a = await Y.dispatch("actionCallLlm", { templateId: "analyzeIncomingMessage", save: !1, context: [{ type: "conversation", value: JSON.stringify(ie.value) }], options: { activeModel: "advanced" } }), { structured: l } = a; l?.key_points && (re.value = l.key_points, ue.value = l.summary, de.value = l.deep_answers, ce.value = l.general_answers), l?.incoming_language && pe("language", l.incoming_language), ne.value = !1 } } catch (e) {} finally { ne.value = !1 }}Gmail DOM extraction
assets/background.js--ICHH-ml.jsscraperGetConversation: async (t, e) => { const n = await l(); if (!n?.url) return; let r = []; return n?.url?.includes("mail.google.com") && (r = await t.dispatch("scraperGetGmailConversation")), { conversation: r }},scraperGetGmailConversation: async (t, e) => { const n = await l(); if (!n?.url) return; const r = await Py.querySelector({ tabId: n.id, selector: '[role="list"]', method: "html", fetchMethod: "tab" }); if (!r) return; return function(t) { try { const e = [".yj6qo.ajU", ".HOEnZb.adL", ".gmail_quote", ".gmail_extra", ".adM", ".ajv", ".h5", ".zFot5d", ".yj6qo", ".adL"], n = st(t); e.forEach(t => { n(t).remove() }); const r = [], i = n('[role="listitem"]'); return i.each((t, e) => { const a = {}; a.isLastMessage = t === i.length - 1; const s = n(e).find("span.gD"); s.length && (a.sender = { name: s.attr("name") || s.text().trim() || "", email: s.attr("email") || "" }); const o = n(e).find(".a3s.aiL"); if (o.length) { let t = o.text().trim(); t = t.replace(/\\n/g, "\n"), t = t.replace(/\s*\n\s*/g, "\n"), t = t.replace(/\n{3,}/g, "\n\n"), t = t.replace(/\s{2,}/g, " "), a.content = t } const c = n(e).find("[title]"); c.length && (a.timestamp = c.attr("title") || ""); const l = n(e).find(".aQH"); l.length && (a.hasAttachments = !0, a.attachmentNames = [], l.each((t, e) => { const r = n(e).find(".aVK").text().trim(); r && a.attachmentNames?.push(r) })), (a.content || a.sender && a.sender.email) && r.push(a) }), r } catch (e) { return [] } }(r)},AI chat route and backend base URL
assets/background.js--ICHH-ml.js and assets/mainConfig-D0q9gJWx.jsapiClient: { call: async (e, n) => { try { let i; return i = t.ai_useApiKey ? await t.dispatch("openaiApiDirect", { ...e, options: { ...e.options, activeModel: (r = e.options?.activeModel, { standard: "gpt-4o-mini", advanced: "gpt-4o", pro: "gpt-5" } [r] || "gpt-4o-mini") } }) : await t.dispatch("meomniApi", { route: n ? "ai/chat/stream" : "ai/chat", method: "POST", data: e, onStream: n }), i?.choices || i?.usage ? et.extractDataFromResponse(i) : i } catch (i) { throw i } var r }}const t = "https://v2-mlqv2syt5a-uc.a.run.app/api/v2";Remote service that receives the AI chat request
- v2-mlqv2syt5a-uc.a.run.app
Receives the extension's AI chat request at /api/v2/ai/chat, including the conversation context assembled from Gmail.
Where it sends data
Destinations our analysis observed Meomni Pro 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.
- v2-mlqv2syt5a-uc.a.run.app
Meomni Pro sends data to v2-mlqv2syt5a-uc.a.run.app. One other extension we have analyzed sends data here.
