Is Chat with all AI models (Gemini, Claude, DeepSeek…) & AI Agents | AITOPIA safe?

Medium risk

AITOPIA is medium risk. When you ask the AI chat to use the page as context, the extension reads it, stores title, URL, favicon, and text, and includes that in a POST to Aitopia. Dynamic analysis didn't complete this flow, so the body is source evidence.…

AITOPIA AI Sidebarv6.9.1Chrome Web Store
52Risk

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

Publishers can request a review.

Findings

Page-context chat sends page contents to AITOPIA

When you ask the AI chat to use the page as context, the extension reads it, stores title, URL, favicon, and text, and includes that in a POST to Aitopia.

Dynamic analysis didn't complete this flow, so the body is source evidence.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You ask the AI chat to use the current page as context.

The extension did this

The extension reads the page and sends the page URL, title, favicon, and extracted text with the chat request.

Page-context fields prepared for the chat request
  • Current page URL
    https://example.com/account/settings?tab=security (illustrative)

    Shows which page you were viewing when you asked the AI to use page context.

  • Page title
    Account security settings (illustrative)

    Adds the page or document title to the chat context.

  • Favicon URL
    https://example.com/favicon.ico (illustrative)

    Identifies the site icon associated with the page being summarized.

  • Extracted page text
    Recent sign-in activity and recovery options are listed below (illustrative)

    Includes readable text from the page so the AI can answer using that page.

Captured request
POSThttps://extensions.aitopia.ai/ai/send

Source-code confirmed POST endpoint; no ai/send body was captured during dynamic analysis because the page-context chat flow was not completed.

Headers
Accept
text/plain
The code that does this

The page-context flow reads the DOM and sends context through aiChat

Readable version

Readable page-context read and send methods

assets/382f08fab624e642e26a9859ec0d1773.js
async onReadPage() {  var t;  try {    if (this.constants.is_web_app) return this.notify.error(this.__("You can't use this feature on this page."));    const e = f.parseMeaningfulContentFromHTML(document.documentElement.innerHTML),      a = f.prepareContentForSearch(e);    if (!e) {      console.error("Unable to read page content.");      return    }    await this.changeStorage("page_context", {      favicon: ((t = document.querySelector('link[rel="icon"]')) == null ? void 0 : t.href) || "",      title: document.title,      url: window.location.href,      content: a    })  } catch (e) {    console.error("Error reading page:", e), console.log("An error occurred while reading the page.")  }},async onSelectPageOption(t, e = null) {  var d, p, m, h, v, g, y, C;  if (!((p = (d = this.storage.Local) == null ? void 0 : d.page_context) != null && p.title)) return;  const a = t && t.requireNavigateTo == "web_search";  a ? this.onWebAccessChange(!0) : this.onWebAccessChange(!1);  const n = {    context: this.storage.Local.page_context.content,    prompt: e ?? t.prompt  };  let o = this.storage.Local.page_context.content;  if (a) {    const S = t.search_prefix.length;    o = this.storage.Local.page_context.content.substring(0, 2040 - S), o = `${o} ${t.search_prefix??e}`  }  let s = {    contextData: {      mode: "pagecontext",      favicon: (h = (m = this.storage.Local) == null ? void 0 : m.page_context) == null ? void 0 : h.favicon,      title: (g = (v = this.storage.Local) == null ? void 0 : v.page_context) == null ? void 0 : g.title,      url: (C = (y = this.storage.Local) == null ? void 0 : y.page_context) == null ? void 0 : C.url,      actionType: t ? {        id: t.id,        label: t.label,        description: t.description      } : {        id: "custom_prompt",        label: "Custom Prompt",        description: e.substring(0, 100)      }    }  };  const u = a ? o : JSON.stringify(n);  this.aiChat(u, this.settings.ai_model, "ai_chat", "__all", !0, !1, null, !1, s), this.changeStorage("page_context", null)}

Readable content extraction helper

assets/f25cd05595e3875a792027c4772abf53.js
parseMeaningfulContentFromHTML(e) {  var t, a, n, s, i, o, l;  const r = (new DOMParser).parseFromString(e, "text/html");  ["script", "style", "iframe", "nav", "header", "footer", "aside", "noscript", "link", "body .ad", "body .advertisement", "body #ads", "body #ad-container", "body .social-share", "body .comments", "body .related-articles", 'body [class*="ads"]', 'body [id*="ads"]', 'body [class*="banner"]'].forEach((e => {    r.querySelectorAll(e).forEach((e => e.remove()))  }));  const c = {    importantTags: {},    meta: {      title: "",      description: ""    },    mainContent: null  };  c.meta.title = (null == (a = null == (t = r.querySelector("title")) ? void 0 : t.textContent) ? void 0 : a.trim()) || "", c.meta.description = (null == (s = null == (n = r.querySelector('meta[name="description"]')) ? void 0 : n.getAttribute("content")) ? void 0 : s.trim()) || "", ["h1", "h2", "h3", "h4", "h5", "h6"].forEach((e => {    const t = r.querySelectorAll(e);    t.length > 0 && (c.importantTags[e] = Array.from(t).map((e => {      var t;      return null == (t = null == e ? void 0 : e.textContent) ? void 0 : t.trim()    })).filter((e => e)).join(" OR "))  }));  const u = ["article", "main", ".content", ".post-content", ".article-content", "#main-content", '[role="main"]'];  for (const d of u) {    const e = r.querySelector(d);    if (e) {      c.mainContent = (null == (i = null == e ? void 0 : e.textContent) ? void 0 : i.trim().replace(/\s+/g, " ").replace(/\n\s*/g, "\n").replace(/\s*\n\s*\n\s*/g, "\n\n")) || null;      break    }  }  return c.mainContent || (c.mainContent = (null == (l = null == (o = null == r ? void 0 : r.body) ? void 0 : o.textContent) ? void 0 : l.trim().replace(/\s+/g, " ").replace(/\n\s*/g, "\n").replace(/\s*\n\s*\n\s*/g, "\n\n")) || null), c}

Readable AI send endpoint construction

assets/f25cd05595e3875a792027c4772abf53.js
let x = `${h.api_url}/ai/send`,  v = null != s.notUndefined(c, "compare_turn") || null != s.notUndefined(c, "variant_group");if (null != this.credit_detail && !v) {  let e = t;  if (this.settings.web_access && (e = "GPT-4o"), typeof this.credit_detail[e] < "u" && this.credit_detail[e].balance < this.credit_detail[e].cost && -999 != this.credit_detail[e].credit && "api" !== this.settings.ai_mode) return this.ai_all_response[n][a].forEach(((e, t) => {    "limit" == e.role && delete this.ai_all_response[n][a][t]  })), this.ai_all_response[n][a] = Object.values(this.ai_all_response[n][a]), this.ai_all_response[n][a].push({    item: "Usage Limit please upgrade plan.",    role: "limit",    model: t  }), await this.setDataStorage(a, n), void(await this.waiting(!1))}
Destination for page-context chat
    • extensions.aitopia.ai

    Receives AI chat requests that include the selected page context.

Undisclosed Amazon Affiliate Registration with captaina.co at Startup

On load, AITOPIA contacts captaina.co, absent from its listing, manifest, and allowlist, getting an Amazon affiliate ID for each of 21 stores, stored under key merchants.

DA confirmed the POST fires at startup, empty body, exposing your IP.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You install this extension or restart your browser.

The extension did this

The service worker contacts captaina.co and registers for an Amazon affiliate key covering 21 regional storefronts, without any notice in the extension listing or manifest.

The POST fires with an empty body, disclosing your IP address. The returned merchant IDs for all 21 Amazon regions are stored in chrome.storage.local.

Captured request
POSThttps://captaina.co/extensions/app/get_key

200 OK, returns JSON with data.key (hopekey API key) and data.licences.markets (object mapping 21 Amazon domain strings to associate IDs); both are written to chrome.storage.local

Headers
Content-Type
application/json
Information exchanged with captaina.co at startup
  • Your IP address (sent)
    203.0.113.42 (illustrative)

    Disclosed via the TCP connection. The request body is empty, but captaina.co's servers still receive your IP address.

  • Affiliate API key received (hopekey)
    f2a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6 (illustrative)

    A credential issued by captaina.co, stored under key hopekey, linking this browser's extension sessions to a captaina.co affiliate account.

  • Amazon Marketplace IDs (21 regions)
    {"amazon.com":"ATVPDKIKX0DER","amazon.co.uk":"A1F83G8C2ARO7P","amazon.de":"A1PA6795UKMFR9","amazon.fr":"A13V1IB3VIYZZH","amazon.co.jp":"A1VC38T7YXB528","amazon.ca":"A2EUQ1WTGCTBG2"}

    Amazon associate tracking IDs per storefront, stored under merchants; insertable into Amazon URLs to attribute purchases to the account.

The code that does this

Automatic captaina.co registration in class E's constructor/setKey() (494d3f253ab4.js)

Readable version
// Module: assets/494d3f253ab426237c2332badda6774b.js (service worker)// Instantiated unconditionally at module load — no user interaction requiredclass CaptainaClient {  captaina_version = '1.1.0';  site_url         = 'https://captaina.co';  api_url          = 'https://_crawl.captaina.co';  // auth/logout endpoint  hopekey          = null;  merchants        = null;  // Hardcoded Amazon associate IDs for 21 regional storefronts  market_urls = {    'amazon.fr':     'A13V1IB3VIYZZH',    'amazon.sa':     'A17E79C6D8DWNP',    'amazon.nl':     'A1805IZSGTT6HS',    'amazon.sg':     'A19VAU5U5O7RUS',    'amazon.com.mx': 'A1AM78C64UM0Y8',    'amazon.pl':     'A1C3SOZRARQ6R3',    'amazon.co.uk':  'A1F83G8C2ARO7P',    'amazon.de':     'A1PA6795UKMFR9',    'amazon.es':     'A1RKKUPIHCS9HS',    'amazon.co.jp':  'A1VC38T7YXB528',    'amazon.in':     'A21TJRUUN4KGV',    'amazon.ca':     'A2EUQ1WTGCTBG2',    'amazon.se':     'A2NODRKZP88ZB9',    'amazon.com.br': 'A2Q3Y263D00KWC',    'amazon.ae':     'A2VIGQ35RCS4UG',    'amazon.com.tr': 'A33AVAJ2PDY3EV',    'amazon.com.au': 'A39IBJ37TRP1C6',    'amazon.com.be': 'AMEN7PMS3EDWL',    'amazon.it':     'APJ6JRA9NG5V4',    'amazon.eg':     'ARBP9OOSHTCHU',    'amazon.com':    'ATVPDKIKX0DER'  };  constructor() {    this.setKey();  // <-- runs immediately; contacts captaina.co on first install    // Build pattern_urls list for use in content scripts on Amazon domains    Object.keys(this.market_urls).forEach(domain => {      this.pattern_urls.push(`https://*.${domain}/*`);      this.marketplace_idBydomain[this.market_urls[domain]] = `https://www.${domain}`;    });  }  setKey() {    // Throttle: skip if called within 1 second of last call    if (this.start_key === 1 || (Date.now() - this.check_key_time) / 1000 <= 1) {      return chrome.storage.local.get('hopekey');    }    this.check_key_time = Date.now();    return chrome.storage.local.get('hopekey').then(stored => {      if (stored.hopekey !== undefined) {        // Key already exists — load it        this.hopekey = stored.hopekey ?? null;        if (this.hopekey == null || this.hopekey === 'false') {          // Invalid key: call auth/logout on _crawl.captaina.co, clear storage          fetch(this.api_url + 'auth/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' } });          chrome.storage.local.remove(['hopekey', 'merchants', 'credits']);        } else {          // Valid key: load previously stored merchants          chrome.storage.local.get('merchants').then(m => {            if (m.merchants !== undefined) this.merchants = m.merchants;          });        }      } else {        // No key yet — register with captaina.co        // Empty POST body; IP address is disclosed through the TCP connection        this.start_key = 1;        fetch(this.site_url + '/extensions/app/get_key', {          method: 'POST',          headers: { 'Content-Type': 'application/json' }        })        .then(r => r.text())        .then(text => {          const response = JSON.parse(text);          if (response.data?.key) {            // Store affiliate API key            chrome.storage.local.set({ hopekey: response.data.key });            if (response.data.licences?.markets) {              // Store Amazon merchant IDs for 21 regions              chrome.storage.local.set({ merchants: response.data.licences.markets });              this.merchants = response.data.licences.markets;            }            this.hopekey = response.data.key;          }          this.start_key = 0;        })        .catch(() => { this.start_key = 0; });      }    });  }}new CaptainaClient();  // Instantiated at module top-level
Domains contacted at startup, not listed in the extension manifest or Chrome Web Store listing
    • captaina.co

    Issues an affiliate key and 21 Amazon marketplace IDs at startup. Absent from host_permissions, externally_connectable, and the listing. Vendor relationship undocumented.

    • _crawl.captaina.co

    Authentication endpoint referenced in class E. Called for auth/logout when the stored hopekey is null or 'false'.

User Preferences Sent to aitopia.ai on Every Page Load

Each navigation, this extension POSTs your preferences (AI model, language, sidebar position, YouTube state), two session tokens, and the runtime ID to extensions.aitopia.ai, firing within seconds of load.

The server returns an API key.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You navigate to any webpage with this extension installed.

The extension did this

The extension POSTs your full settings object, including AI model, language, and UI preferences, along with two persistent session tokens to extensions.aitopia.ai.

This fires within seconds of page load, before you interact with any AI feature. The extension runtime identifier is also included in the request body.

Fields transmitted to extensions.aitopia.ai on every page navigation
  • Session token (uuid_hopekey)
    4df6ae12a38d3c36e60dfd10516763ff

    A persistent token that lets the server associate all your past and future requests with the same browser profile.

  • Session search token (uuid_search)
    c7e3a1d942f8b056e2197f43a8d6c0b1e5f92847 (illustrative)

    A second persistent token included in every request to the aitopia.ai backend to track your session across navigations.

  • Extension runtime ID
    becfinhbfclcgokjlobojlnldbfillpf

    The Chrome Web Store ID of this extension, sent with every request. This is the same value for all users of the extension.

  • Full settings object
    {"ai_model":"AITOPIA","color_mode":"dark","language":"en","sidebar_position":"right","youtube":false,"web_access":false,"context_reading":true,"context_writing":true}

    Your full extension preferences: AI model, UI language, sidebar position, YouTube state, web access toggle, and reading/writing modes.

  • Extension version and build type
    v=6.8.0, bt=self

    Identifies the exact version and distribution channel of the extension you have installed.

Captured request
POSThttps://extensions.aitopia.ai/extensions/app/get_key

200 OK, returns uuid_hopekey (API key), uuid_search, credit_usage, credit_detail, licences, plan, and google_auth_uri; written to chrome.storage.local

Headers
Content-Type
application/json
Body
{  "ai_mode": "local",  "lang": "en",  "model": "AITOPIA",  "settings": {    "ai_model": "AITOPIA",    "color_mode": "dark",    "language": "en",    "sidebar_position": "right",    "youtube": false,    "web_access": false,    "context_reading": true,    "context_writing": true  },  "uuid_hopekey": "4df6ae12a38d3c36e60dfd10516763ff",  "uuid_search": "<redacted>",  "v": "6.4.0",  "ri": "becfinhbfclcgokjlobojlnldbfillpf",  "bt": "self"}
The code that does this

uuid() collects settings and calls get_key; apiCall() injects runtime fields before every request

Readable version

uuid() — annotated readable form

// Called by the content script on every page load initializationuuid = async (force = 0, extraData = {}) => {  // Read all local storage  const storage = await chrome.storage.local.get();  const settings = storage.settings ?? DEFAULT_SETTINGS;  // Build request payload from stored user preferences  const payload = {    ai_mode:  settings.ai_mode,    // e.g. 'local', 'api'    lang:     settings.language,   // e.g. 'en'    model:    settings.ai_model,   // e.g. 'AITOPIA', 'claude-3-5-sonnet'    settings: settings             // the full preferences object  };  // apiCall() will also append before sending:  //   ri           = chrome.runtime.getManifest().id  (extension CWS ID)  //   bt           = build_type ('self')  //   v            = version string ('6.8.0')  //   uuid_hopekey = stored per-user API key  //   uuid_search  = stored per-user session token  return this.apiCall('/extensions/app/get_key', payload, 'POST');};

apiCall() — runtime identifier and session token injection (annotated)

// Executed for every non-GET request before the fetch() call:if (constants.runtime_id != null) {  payload.v  = constants.version;                    // extension version  payload.ri = chrome.runtime.getManifest().id;      // extension CWS ID  payload.bt = constants.build_type;                 // 'self'}// Append per-user session tokens from storage if not already present:if (typeof payload.uuid_search === 'undefined') {  payload.uuid_search  = storage.Local.uuid_search;  // long opaque session token}if (typeof payload.uuid_hopekey === 'undefined') {  payload.uuid_hopekey = storage.Local.uuid_hopekey; // short API key}
Destination receiving your settings and session tokens
    • extensions.aitopia.ai

    Aitopia's backend. Receives the full settings object, two persistent session tokens, and the runtime ID on every navigation; returns an API key, credit balance, and license.

+2 more findings not shown

Updated 30 September 2026becfinhbfclcgokjlobojlnldbfillpf