Is Smart Sidebar: Chat GPT, Claude & DeepSeek safe?
Smart Sidebar is high risk. When you use document chat with a PDF, Word, Excel, or PowerPoint file, the extension extracts page text into a document-wide string and adds it to the AI prompt context, then sends the chat request to Aitopia's endpoint.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Document Chat Sends Extracted File Text to Aitopia
When you use document chat with a PDF, Word, Excel, or PowerPoint file, the extension extracts page text into a document-wide string and adds it to the AI prompt context, then sends the chat request to Aitopia's endpoint.
You upload a document and use the extension's document chat feature.
The extension extracts text from the file and includes that text in an AI chat request.
| Field | Value | Why it matters | |
|---|---|---|---|
Extracted page text | Q3 customer renewal plan: Acme Corp, renewal date 2026-08-01, owner Morgan Lee. (illustrative) | The text inside the uploaded file can become part of the prompt sent for analysis. | |
File name context | Quarterly-plan.pdf - Chat with File | The request context can include the file name associated with the chat session. | |
File attachment ID | file_9d4e2f7a61b34c2a | When the upload API returns an ID, the chat request can reference that uploaded file along with the extracted text. |
Document extraction, prompt building, and POST construction
async handleFileUpload(e) {
const t = e.target.files[0];
if (!t) return;
if (!["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation"].includes(t.type)) {
this.notify.error(this.__("Please upload a PDF, Word, Excel, or PowerPoint file"));
return
}
try {
const n = await N.set(t, null, null);
n && (n._listkey || n.id || n.file_id) ? this.uploadedFileId = n._listkey || n.id || n.file_id : this.uploadedFileId = null
} catch (n) {
console.error("Error uploading file via /files/create", n), this.uploadedFileId = null
}
await this.cleanupResources(), this.newChat(null, "ai_single", b.SHA1("chat_with_file")), this.isLoading = !0, this.error = null, this.fileName = t.name;
try {
console.log("Loading document file..."), this.isConverting = !t.type.includes("pdf");
const n = 10 * 1024 * 1024;
if (t.size > n) {
this.notify.error(this.__("File is too large. Please upload a smaller file."));
return
}
await this.fileProcessor.loadPDF(t), this.isConverting = !1, console.log("Document loaded successfully");
const i = this.fileProcessor.getPdfDoc();
if (i) {
let o = "";
const p = i.numPages,
w = Math.ceil(p / 5);
for (let f = 0; f < w; f++) {
const k = f * 5 + 1,
v = Math.min((f + 1) * 5, p);
for (let u = k; u <= v; u++) {
const P = await i.getPage(u),
s = await P.getTextContent(),
x = s.items.map(S => S.str).join(" ");
o += x + `
`, await P.cleanup(), s.items.length = 0, Object.keys(s).forEach(S => {
s[S] = null
})
}
await new Promise(u => setTimeout(u, 0))
}
this.pdfContent = o, this.context_data = {
mode: "chatwithfile",
fileName: `${this.fileName} - Chat with File`,
contextData: {
context: this.pdfContent
}
}
}
if (this.totalPages = this.fileProcessor.numPages, console.log("Total pages:", this.totalPages), this.totalPages === 0) throw new Error("No pages found in document");
this.currentPage = 1, this.fileUploaded = !0, await this.renderThumbnailsWithMemoryManagement(), await this.$nextTick(), await this.initializeContainerWithRetry(), await this.getFileAnalysis(), await b.Browser().storage.local.set({
current_file_name: t.name
}), console.log("Starting page render..."), await this.renderCurrentPage(), console.log("Page rendered successfully")
} catch (n) {
console.error("Error processing file:", n), this.error = n.message || this.__("Error processing file"), await this.cleanupResources()
} finally {
console.log("Setting isLoading to false"), this.isLoading = !1, this.isConverting = !1;
const n = e.target;
n.value = ""
} async getFileAnalysis() {
const e = "fileAnalysis";
this.suggestionsKey = e;
try {
const t = await b.Browser().storage.local.get("current_file_name");
if (this.ai_single && this.ai_single[e] && t && t.current_file_name === this.fileName) return;
this.isGettingSuggestions = this.isGettingSummary = !0;
let r = `You are a highly skilled AI assistant for analyzing file content. I need two things:
1. A summary of the content in maximum 2 very short sentences. Start the summary with ##SUMMARY_START## and end with ##SUMMARY_END##
2. Three topic-based question suggestions about the content. Each suggestion should focus on different aspects (one positive, one neutral, one negative) to help understand the file content better. Start each suggestion with ##SUGGESTION_START## and end with ##SUGGESTION_END##
Do not include labels like "positive", "negative", or "neutral" in the output. Keep suggestions concise but informative.
Respond in the ${this.settings.language_name} language.
Content to analyze:
"""${this.pdfContent}"""`;
this.running = !0;
let n = Object.assign({}, this.emailData);
n.mode = "fileAnalysis", this.uploadedFileId && (n.file_ids = [this.uploadedFileId]), console.log("Getting file analysis...");
const i = this.uploadedFileId != null ? [this.uploadedFileId] : void 0;
await this.aiChat(r, this.settings.ai_model, "ai_single", e, !0, !0, null, !1, n, i), console.log("File analysis completed.")
} catch (t) {
console.error("Error in getFileAnalysis:", t), this.notify.error(this.__("Error happened while analyzing file content"))
} finally {
this.isGettingSuggestions = this.isGettingSummary = !1, this.running = !1
}
}, async chat(o = "", e = null, t = "ai_chat", i = "__all", n = !1, a = !1, l = null, r = !1, c = {}, f = !1, d = 0, p, h) {
var te, ie;
if (this.ai_wait) return;
typeof this.aborting[i] < "u" && (this.aborting[i] = null);
let _ = await s.Browser().storage.local.get(["settings", "credit_detail", "expires", "ai_image_url", "ai_chat_ids", "context_data", "selected_agent", t]);
if (this.chat_selected_agent = s.notUndefined(_, "selected_agent"), await s.Browser().storage.local.set({
ai_ask: null
}), s.notUndefined(_, "context_data") && !(c != null && c.mode)) {
const T = window.location.pathname,
x = (te = _.context_data) == null ? void 0 : te.mode,
H = {
chatwithfile: "chat_with_file.html"
};
x && H[x] && T.endsWith(H[x]) && ((ie = _.context_data) != null && ie.contextData && (o = `##HIDDEN_CONTEXT_START##${_.context_data.contextData.context}##HIDDEN_CONTEXT_END##` + o), t = "ai_single", i = s.SHA1("chat_with_file"), n = !1, a = !1)
}
this.key_detail = await s.Browser().storage.local.get(["uuid_hopekey", "uuid_search"]), this.settings = s.notUndefined(_, "settings"), e == null && (e = g.ai_default_model);
let v = this.settings.ai_image_model == e ? "image" : "text";
e === null && (e = this.settings.ai_model), this.waiting(!0), this.credit_detail = s.notUndefined(_, "credit_detail"), typeof this.ai_all_response[i] > "u" && (this.ai_all_response[i] = {}), typeof this.ai_all_response[i][t] > "u" && (this.ai_all_response[i][t] = []);
let w = s.notUndefined(_, `${t}`);
typeof _.expires != null && (this.expires = _.expires);
let S = {};
typeof w == "string" ? (S = JSON.parse(w), Object.keys(S).length > 0 && Object.keys(S).forEach(T => {
typeof this.ai_all_response[T] > "u" && (this.ai_all_response[T] = {}), this.ai_all_response[T][t] = Object.values(S[T])
})) : (S = w, S != null && (t == "ai_chat" ? this.ai_all_response[i][t] = Object.values(S) : typeof S[i] < "u" && (this.ai_all_response[i][t] = Object.values(S[i])))), t == "ai_single" && n == !0 && r == !1 && typeof this.ai_all_response[i] < "u" && typeof this.ai_all_response[i][t] < "u" && (this.ai_all_response[i][t] = [], Object.keys(this.ai_all_response[i][t]).length > 0), this.uuid_search = s.notUndefined(this.key_detail, "uuid_search"), this.hopekey = s.notUndefined(this.key_detail, "uuid_hopekey"), document.querySelector("#aifnmjmchg-container");
let y = `${g.api_url}/ai/send`;
if (this.credit_detail != null) {
let T = e;
if (this.settings.web_access && (T = "GPT-4o"), typeof this.credit_detail[T] < "u" && this.credit_detail[T].balance < this.credit_detail[T].cost && this.credit_detail[T].credit != -999 && this.settings.ai_mode !== "api") {
this.ai_all_response[i][t].forEach((x, H) => {
x.role == "limit" && delete this.ai_all_response[i][t][H]
}), this.ai_all_response[i][t] = Object.values(this.ai_all_response[i][t]), this.ai_all_response[i][t].push({
item: "Usage Limit please upgrade plan.",
role: "limit",
model: e
}), await this.setDataStorage(t, i), await this.waiting(!1);
return
}
}
let O = this.ai_all_response[i][t],
b = 0,
m = !1;
Object.keys(this.ai_all_response[i][t]).length > 0 && !s.notUndefined(c, "search") && this.ai_all_response[i][t].forEach((T, x) => {
b++, O != b && this.ai_all_response[i][t][x].loading == !0 && (this.ai_all_response[i][t][x].loading = !1, m = !0), T.role == "limit" && (delete this.ai_all_response[i][t][x], this.ai_all_response[i][t] = Object.values(this.ai_all_response[i][t]), m = !0)
}), f && (this.is_group_chat = !0, m = !0), m && await this.setDataStorage(t, i);
let U = "";
this.count++;
let B = "user";
if (v == "image" && (B = "image"), (c == null || typeof c != "object") && (c = {}), (String(o).trim().length > 0 || B == "image") && r == !1 && (c.prompt_mode = a, d == 0)) {
const T = {
item: o,
role: B,
model: e,
title: l,
loading: B == "image",
extra_data: c
};
h && Array.isArray(h) && h.length > 0 && (T.attachments = h), this.ai_all_response[i][t].push(T)
}
v != "image" && s.notUndefined(_, "ai_image_url") != null && s.notUndefined(_, "ai_image_url") !== "" && typeof c.search_mode > "u" && (this.settings.web_access = !1, c.ai_image_url = s.notUndefined(_, "ai_image_url"), await s.Browser().storage.local.set({
ai_image_url: null
}), await s.Browser().storage.local.remove(["ai_image_url"]));
let k = s.ChatId(t, i);
const $ = p && Array.isArray(p) && p.length > 0 ? p : void 0;
let A = {
history: this.ai_all_response[i][t],
text: o,
model: e,
stream: v != "image",
uuid_search: this.uuid_search,
mode: t,
prompt_mode: a,
extra_key: i,
extra_data: c,
chat_id: k,
language_detail: this.settings.language_detail,
is_continue: r
};
$ && $.length > 0 && (A.file_ids = $);
const L = this.settings.capabilities;
L && typeof L == "object" && Object.values(L).some(T => T === !0) && (A.settings = L), this.uuid_extra_data = {
chat_id: A.chat_id,
history: A.history,
mode: t
}, this.settings.ai_key && this.settings.ai_mode == "api" && (A.token = this.settings.ai_key), this.settings.language && (A.lang_code = this.settings.language);
let q = this.ai_all_response[i][t].length;
(v == "image" || s.notUndefined(c, "search")) && (q = q - 1), q < 0 && (q = 0);
let R = "";
r == !0 && this.ai_all_response[i][t].forEach((T, x) => {
typeof T.finish_reason < "u" && T.finish_reason == "length" && (T.finish_reason = "", q = x, U = R = T.item)
}), this.ai_all_response[i][t].forEach((T, x) => {
typeof T.finish_reason < "u" && T.finish_reason == "length" && (T.finish_reason = "stop")
}), v != "image" && (this.ai_all_response[i][t][q] = {
item: R,
role: "system",
model: e,
loading: !0,
title: l,
extra_data: c
}), await this.setDataStorage(t, i), this.aborting[i] = new AbortController;
let V = document.querySelectorAll("#aifnmjmchg-stop-chat-" + t),
G = document.querySelectorAll("#aifnmjmchg-stop-chat-" + t + " button");
G != null && V != null && (V.forEach(T => {
T.classList.remove("aifnmjmchg-hide"), T.classList.add("aifnmjmchg-show")
}), this.aborting[i].signal.addEventListener("abort", () => {
t == "ai_single" ? Object.values(this.ai_all_response[i][t]).length > 0 && this.ai_all_response[i][t].forEach((T, x) => {
this.ai_all_response[i][t][x].loading = !1, this.ai_all_response[i][t][x].stop = !0
}) : (this.ai_all_response[i][t][q].loading = !1, this.ai_all_response[i][t][q].stop = !0), this.setDataStorage(t, i), this.waiting(!1)
}), Object.keys(G).length > 0 && G.forEach(T => {
T.onclick = x => {
this.aborting[i].abort(), this.waiting(!1);
let H = {
force: 1
};
this.is_group_chat && (H.extra_data = this.uuid_extra_data), s.sendMessage("uuid", H), V.forEach(Y => {
Y.classList.add("aifnmjmchg-hide"), Y.classList.remove("aifnmjmchg-show")
})
}
}));
let ee = null;
return await this.setDataStorage(t, i), ["image"].indexOf(v) !== -1 ? await this.imageApi(t, e, i, A, q, V, ee, y, U).finally(async () => await this.waiting(!1)) : s.notUndefined(this.settings, "web_access") == !0 && ["image"].indexOf(e) === -1 && r == !1 && i == "__all" && !s.notUndefined(c, "search") && o.length <= 2048 && a == !1 ? (s.Browser().web_access_mode = t, s.Browser().web_access_extra_key = i, s.sendMessage("GoogleSearchCall", o, "GoogleSearchCallback", "tabs")) : (s.notUndefined(_, "selected_agent._listkey") && !a && (A.extra_data.agent_id = s.notUndefined(_, "selected_agent._listkey")), await this.aiApi(t, e, i, A, q, V, ee, y, U, this).finally(async T => {
await this.waiting(!1)
}))
}, async aiApi(o, e, t, i, n, a, l, r, c, f = this) {
let d = 0;
return s.notUndefined(this.ai_all_response, `${t}`) || (f.ai_all_response[t] = {}), s.notUndefined(this.ai_all_response, `${t}.${o}`) || (f.ai_all_response[t][o] = {}), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) || (f.ai_all_response[t][o][n] = {}), (this.hopekey ?? null) === null && (this.key_detail = await s.Browser().storage.local.get(["uuid_hopekey", "uuid_search"]), this.uuid_search = s.notUndefined(this.key_detail, "uuid_search"), this.hopekey = s.notUndefined(this.key_detail, "uuid_hopekey")), fetch(r, {
headers: {
accept: "text/plain",
hopekey: this.hopekey || null
},
body: JSON.stringify(i),
method: "POST",
signal: this.aborting[t].signal
}).then(async p => {
const _ = p.body.getReader();
let v = async () => _.read().then(async ({
value: w,
done: S
}) => {
if (await f.streamFormatter(w, S, t, o, n, d, f), d++, !S) return await v()
}).catch(w => {
if (f.waiting(!1), w instanceof DOMException && w.name == "AbortError") {
document.querySelector(".ai_loading") != null && document.querySelector(".ai_loading").classList.remove("ai_loading"), f.ai_all_response[t][o][n]._abort = !0;
return
} else w.message == "network error" ? (f.ai_all_response[t][o][n].loading = !1, f.ai_all_response[t][o][n]._error = JSON.stringify(w), console.log(w.message)) : (f.ai_all_response[t][o][n].loading = !1, console.log(w.message, w.toString(), w), console.log(f.ai_all_response[t][o]), console.log(c ?? null), f.ai_all_response[t][o][n]._error = JSON.stringify(w));
f.setDataStorage(o, t)
});
return await v()
}).then(p => {}).catch(p => {
this.waiting(!1), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) && (this.ai_all_response[t][o][n].loading = !1, this.ai_all_response[t][o][n].loading = !1);
let h = '<span class="aifnmjmchg-text-red-600">' + s.__("(Internet connection lost. Please try again)") + "</span>";
a != null && a.forEach(_ => {
_.classList.add("aifnmjmchg-hide"), _.classList.remove("aifnmjmchg-show")
}), c.length > 0 && (h = c + `
` + h), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) && (this.ai_all_response[t][o][n].item = h, f.ai_all_response[t][o][n]._error = JSON.stringify(p), this.setDataStorage(o, t)), console.log(p)
})
},- beta_api.aitopia.ai
Aitopia API endpoint that receives document chat requests for response generation.
Gmail AI Reply Sends Thread Content to Aitopia
When you use AI reply in Gmail, the extension reads the selected message body, quoted history, sender/receiver, and page context.
The confirmed code path passes these into the AI request posted to Aitopia.
You ask the extension to draft an AI reply in Gmail.
The extension reads the active email thread and passes the collected fields into an AI request.
| Field | Value | Why it matters | |
|---|---|---|---|
Visible message body | Can you review the attached proposal before the 3 PM client call? (illustrative) | This can include the text you are reading or replying to in the current Gmail thread. | |
Quoted thread history | On Tue, Alex Rivera wrote: Please confirm the renewal terms for Acme Corp. (illustrative) | Earlier messages in the same conversation can be included with the new reply context. | |
Sender email address | alex.rivera@example-company.com | The sender address ties the conversation content to a specific person or organization. | |
Receiver email address | morgan.lee@example-company.com | The receiver address identifies who is participating in the email thread. |
Gmail selectors, collection, and POST construction
ai_reply: {
"mail.google.com": {
reply_button: ".ams.bkH",
button: ".amn",
container: '[role="listitem"]:last-child',
alternative_container: '[role="listitem"]:last-child',
edit_container: '[role="listitem"] .editable',
reply_insert_container: ".Am.Al.editable.LW-avf",
dot_position: {
bottom: "25%",
left: "85%"
},
detail_elements: [{
name: "history",
selector: ".adL.gmail_quote,.adL .gmail_quote",
mode: "simple",
type: "textContent"
}, {
name: "sender_email",
selector: ".gD[email]",
mode: "attribute",
type: "email"
}, {
name: "sender_name",
selector: ".gD[name]",
mode: "attribute",
type: "name"
}, {
name: "receiver_email",
selector: ".g2[email]",
mode: "attribute",
type: "email"
}, {
name: "receiver_name",
selector: ".g2[name]",
mode: "attribute",
type: "name"
}, {
name: "email_content",
selector: ".a3s > *",
mode: "simple",
type: "innerText"
}]
}, getData(t = null, e = null, a = !1, l = !1) {
if (z.ai_reply[window.location.hostname]) {
const {
reply_button: o,
button: s,
container: u,
alternative_container: d,
edit_container: p,
reply_insert_container: m,
dot_position: h,
detail_elements: v
} = z.ai_reply[window.location.hostname];
if (l && this.last_data !== null && document.querySelector(p) !== null && document.querySelector("#aifnmjmchg-reply-wrapper-dot") !== null && this.last_data.email_content.length > 3) return this.last_data;
t.EmailReply.reply_edit_container = null;
let g = document.querySelector(u);
if (g == null && d !== null && (g = document.querySelector(d)), g == null) return t.draggableModalToggle("ai-reply-popup", !1), t.draggableModalToggle("ai-reply-voice-settings", !1), this.reply_insert_text = t.EmailReply.reply_insert_text = null, null;
if (t.EmailReply.keys = this.buildKeys(), this.keys = this.buildKeys(), g.querySelector(s) !== null ? (this.reply_button_container = null, this.reply_button_container = g.querySelector(s)) : document.querySelector(s) !== null && (this.reply_button_container = null, this.reply_button_container = document.querySelector(s)), this.dot_position = h, this.reply_edit_container = null, document.querySelector(p) !== null ? this.reply_edit_container = document.querySelector(p) : e !== null && (this.first_show = !1, t.draggableModalToggle("ai-reply-popup", !1), this.reply_insert_text = t.EmailReply.reply_insert_text = null), document.querySelector(m) !== null && (this.reply_insert_container = document.querySelector(m)), v && g !== null) {
v.forEach(C => {
let S = [];
g.querySelectorAll(C.selector) !== null && (g.querySelectorAll(C.selector).forEach(A => {
S.push(f.onSettingTypeByValue(C, A))
}), z.ai_reply_data || (z.ai_reply_data = {}), typeof z.ai_reply_data[f.SHA1(window.location.hash)] > "u" && (z.ai_reply_data[f.SHA1(window.location.hash)] = {}), z.ai_reply_data[f.SHA1(window.location.hash)][C.name] = f.removeEmptyLines(S.join(`
`)))
});
let w = z.ai_reply_data[f.SHA1(window.location.hash)];
return this.last_data && (this.last_data.email_content.length > 0 ? (w == null && (w = this.last_data), w.email_content || (w = this.last_data), w.email_content.length == 0 && (w = this.last_data), z.ai_reply_data[f.SHA1(window.location.hash)] = w) : this.last_data = null), this.last_data = w ?? this.last_data, !this.first_show && e !== null && this.last_data !== null && this.reply_edit_container !== null && (this.first_show = !0), a ? z.ai_reply_data[f.SHA1(window.location.hash)] : z.ai_reply_data
}
}
return null
}, async sendSingleAi(t, e, a, l, o = null) {
let s = z.reply_prompts[e],
u = `${window.location.hash}#${a}`;
if (typeof t.ai_prompts[`id_${s}`] < "u") {
let d = t.ai_prompts[`id_${s}`],
p = f.SHA1(u);
await f.Browser().storage.local.get("delete_key").then(h => {
let v = [];
if (typeof h.delete_key < "u" && (v = h.delete_key), typeof v != "object" && (v = []), v.indexOf(p) === -1) return v.push(p), f.Browser().storage.local.set({
delete_key: v
})
}), t.ai_single != null && typeof t.ai_single == "object" && typeof t.ai_single[p] < "u" && (t.ai_single[p] = {});
let m = Object.assign({}, l);
return m.mode = e, await He.build(t, u, o ?? l.email_content, z.ai_default_model, d.prompt, null, m, !1, s)
}
}, async aiApi(o, e, t, i, n, a, l, r, c, f = this) {
let d = 0;
return s.notUndefined(this.ai_all_response, `${t}`) || (f.ai_all_response[t] = {}), s.notUndefined(this.ai_all_response, `${t}.${o}`) || (f.ai_all_response[t][o] = {}), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) || (f.ai_all_response[t][o][n] = {}), (this.hopekey ?? null) === null && (this.key_detail = await s.Browser().storage.local.get(["uuid_hopekey", "uuid_search"]), this.uuid_search = s.notUndefined(this.key_detail, "uuid_search"), this.hopekey = s.notUndefined(this.key_detail, "uuid_hopekey")), fetch(r, {
headers: {
accept: "text/plain",
hopekey: this.hopekey || null
},
body: JSON.stringify(i),
method: "POST",
signal: this.aborting[t].signal
}).then(async p => {
const _ = p.body.getReader();
let v = async () => _.read().then(async ({
value: w,
done: S
}) => {
if (await f.streamFormatter(w, S, t, o, n, d, f), d++, !S) return await v()
}).catch(w => {
if (f.waiting(!1), w instanceof DOMException && w.name == "AbortError") {
document.querySelector(".ai_loading") != null && document.querySelector(".ai_loading").classList.remove("ai_loading"), f.ai_all_response[t][o][n]._abort = !0;
return
} else w.message == "network error" ? (f.ai_all_response[t][o][n].loading = !1, f.ai_all_response[t][o][n]._error = JSON.stringify(w), console.log(w.message)) : (f.ai_all_response[t][o][n].loading = !1, console.log(w.message, w.toString(), w), console.log(f.ai_all_response[t][o]), console.log(c ?? null), f.ai_all_response[t][o][n]._error = JSON.stringify(w));
f.setDataStorage(o, t)
});
return await v()
}).then(p => {}).catch(p => {
this.waiting(!1), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) && (this.ai_all_response[t][o][n].loading = !1, this.ai_all_response[t][o][n].loading = !1);
let h = '<span class="aifnmjmchg-text-red-600">' + s.__("(Internet connection lost. Please try again)") + "</span>";
a != null && a.forEach(_ => {
_.classList.add("aifnmjmchg-hide"), _.classList.remove("aifnmjmchg-show")
}), c.length > 0 && (h = c + `
` + h), s.notUndefined(this.ai_all_response, `${t}.${o}.${n}`) && (this.ai_all_response[t][o][n].item = h, f.ai_all_response[t][o][n]._error = JSON.stringify(p), this.setDataStorage(o, t)), console.log(p)
})
},- beta_api.aitopia.ai
Aitopia API endpoint that receives the AI reply request for response generation.
Extension plants a persistent tracking cookie on its own website
After you sign in, the extension writes a 360-day cookie hopekey (your account's auth UUID) and eref (your extension ID) onto aitopia.ai and chatgptextension.ai.
Any page there can read these, tying browsing to your install with no prompt.
You log into the extension and later browse to its own website, aitopia.ai.
No further action or consent prompt is needed once you're signed in.
The extension writes your account UUID and its own extension ID into cookies on that site.
The cookies are marked Secure and SameSite=None with a 360-day expiry, so they persist and travel with normal browser requests to the site for a year.
| Field | Value | Why it matters | |
|---|---|---|---|
Your account identifier | hopekey=7bd11760f579475641830f8f7dab666c | The same UUID the extension uses internally to identify your account is exposed to the website as a plain cookie value. | |
Which exact extension you installed | eref=fnmihdojmnkclgjpcoonokmkhjpjechg | The extension's Chrome Web Store runtime ID, written as a cookie so the site can distinguish this install from other white-label variants. | |
Extension-installed flag | aifnmjmchg_app=true | A boolean flag confirming this browser has the extension installed and active. |
window.setCookie() called from the extension's own bundled page script
window.setCookie = (name, value, days, domain = null) => {
let expiresStr = "";
if (days) {
const d = new Date();
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
expiresStr = "; expires=" + d.toUTCString();
}
if (domain == null) {
domain = window.location.host;
if (window.location.host.split(".").length > 2) {
domain = "." + window.location.host.split(".").slice(1).join(".");
}
}
document.cookie = `${name}=${value || ""}; ${expiresStr}; path=/; domain=${domain}; secure; SameSite=None`;
};
// Called for each allowed domain (aitopia.ai, chatgptextension.ai):
config.domainAllows.forEach(domain => {
window.setCookie("hopekey", account.uuidHopekey, 360, "." + domain);
if (browser.runtime.id !== manualExtensionId && browser.runtime.id) {
window.setCookie("eref", browser.runtime.id, 360, "." + domain);
}
window.setCookie("aifnmjmchg_app", true, 360, "." + domain);
});- aitopia.ai
The extension vendor's own marketing/account site. Can read hopekey (your account UUID) and eref (your exact extension build) on every page load for 360 days.
- chatgptextension.ai
A second first-party domain operated by the same vendor, receiving the identical cookie set.
+1 more finding not shown
What it can do
Permissions this extension asks for, as declared in version 2.0.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2.0.7, which we have not unpacked yet.
Read and change your data on every site you visit
<all_urls>
Store data in your browser
storage
Run its own code inside the pages you visit
scripting
Read information about your displays
system.display
Add items to the right-click menu
contextMenus