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.
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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
Current page URL | https://company.example/wiki/2026-product-plan (illustrative) | Identifies the exact page you asked the extension to summarize or process. | |
Page title | 2026 Product Plan - Internal Wiki (illustrative) | Adds a readable label for the page you are viewing. | |
Extracted page text | Launch goals, customer notes, and draft timeline for Q3 planning (illustrative) | Can include the article, document, ticket, or form text visible in the active page. |
| x-app-id | fnfdomooadjpfohbepiaonnbdmkdjiog |
| x-app-version | 5.0.27 |
Shipped code path from Web AI action to page-text POST
h = 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"
})
}
}case "getRawHtml": return Promise.resolve(document.documentElement.outerHTML);
actionGetActiveTabReader: 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 || ""
}
}getVariableResult: 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
}apiClient: {
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
}
}import {
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"
}
}- 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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
Sender name and email | Maya Patel <maya.patel@example.com> (illustrative) | This can identify who is participating in the email thread you are viewing. | |
Message body text | Hi 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 timestamp | Jul 12, 2026, 10:42 AM (illustrative) | This shows when messages in the thread were sent or received. | |
Attachment names | Q3-launch-plan.pdf (illustrative) | File names can reveal projects, customers, or subjects discussed in the thread. |
The shipped code gates on Gmail, scrapes the thread, and sends it to AI chat
const 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
}
}scraperGetConversation: 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)
},apiClient: {
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";- 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.