Is MindStudio safe?

High risk

MindStudio is high risk. When the MindStudio side panel requests launch variables, the content script reads page URL, selected content, cleaned HTML, page text, and metadata, then messages it to an app.mindstudio.ai frame. One run failed to capture the HTTP body.

72Risk
Who publishes it

GoMeta, Inc. - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Declared legal entity
GoMeta, Inc.
Registered address
8605 Santa Monica Boulevard, PMB 84730, West Hollywood, CA 90069, US
Registered contact
Luis Chavez

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

Publishers can request a review.

Findings

MindStudio forwards page DOM and metadata to its iframe

When the MindStudio side panel requests launch variables, the content script reads page URL, selected content, cleaned HTML, page text, and metadata, then messages it to an app.mindstudio.ai frame.

One run failed to capture the HTTP body.

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

You use the MindStudio side panel on a web page.

The side panel asks the content script for launch variables tied to the active tab.

The extension did this

The extension reads page content and forwards the bundle to the MindStudio app iframe.

The bundle includes the current URL, selected content, cleaned HTML, full text, metadata, and optional PDF rehosting path.

Launch-variable fields collected from the active page
  • Page URL
    https://intranet.example.com/projects/q3-roadmap?view=private

    This identifies the exact page where you used the side panel.

  • Selected content
    Renewal quote: ACME-2026-07

    If you selected text or form content, that selected fragment can be included in the bundle.

  • Cleaned page HTML
    <main><h1>Q3 roadmap</h1><p>Launch date: July 31</p></main>

    This can reveal the structure and visible contents of the page you are viewing.

  • Full page text
    Q3 roadmap Launch date: July 31 Owner: Product Operations

    This captures the visible text from the page, not just the part you typed into MindStudio.

  • Page metadata
    {"title":"Q3 Roadmap","description":"Internal planning page"}

    Titles, descriptions, Open Graph tags, icons, colors, and fonts add context about the page and site.

Remote destination involved in the side-panel flow
    • app.mindstudio.ai

    Embedded MindStudio app iframe that receives launchVariables from the extension side panel.

The code that does this

The side panel requests launch variables and forwards them to the app iframe

Readable version

Readable side-panel request and forward path from deobfuscated sidepanel.js

sidepanel.js
n.listen("remote/request_launch_variables", (async () => {  console.info("[MindStudio][Sidepanel] Sidepanel frame requested launch variables for current page"), chrome.tabs.query({    active: !0,    currentWindow: !0  }, (async e => {    if (e.length > 0 && e[0].id) {      console.info(`[MindStudio][Sidepanel] Sending launch variable request to content script on tab: ${e[0].id}`);      try {        await chrome.tabs.sendMessage(e[0].id, {          type: "@@mindstudio/remote/request_launch_variables",          _MindStudioEvent: "@@mindstudio/remote/request_launch_variables"        })      } catch (e) {        console.info("[MindStudio][Sidepanel] Failed to get launch variables from tab, sending default", e), n.send(u.ElementId.FRAME, "remote/resolved_launch_variables", {          launchVariables: {            url: "",            rawHtml: "",            fullText: "",            metadata: "",            userSelection: null          }        })      }    }  }))})), t.listen("launcher/resolved_launch_variables", (e => {  console.info("[MindStudio][Sidepanel] Received launch variables for current page, sending to history frame");  const {    launchVariables: t  } = e;  n.send(u.ElementId.FRAME, "remote/resolved_launch_variables", {    launchVariables: t  })}))

Readable iframe creation from deobfuscated sidepanel.js

sidepanel.js
super({  id: u.ElementId.FRAME,  src: `${e}${i}?__displayContext=extension&__controlledAuth=1`,  container: t}), this.tabId = n, this.element.style.cssText = "\n      width: 100%;\n      height: 100%;\n      border: none;\n      overflow: hidden;\n    ", this.setupEventListeners()
The code that does this

The content script collects URL, DOM, text, and metadata

Readable version

Readable launch-variable handler from deobfuscated content.js

content.js
h.listen("remote/request_launch_variables", (async () => {  console.info("[MindStudio][Launcher] Side panel requested launch variables");  const t = S.getSelectedContent(),    e = S.cleanDOM(),    n = S.getCleanTextContent(),    i = S.getMetadataBundle();  let s;  (window.location.href.endsWith(".pdf") || document.querySelector("embed")?.type.includes("pdf")) && (console.info("Rehosting PDF for extraction"), s = await S.getRehostedPdfSecurePath());  const a = {    url: window.location.href,    userSelection: t,    rawHtml: e,    fullText: n,    metadata: i,    _rehostedPdfPath: s  };  console.info("[MindStudio][Launcher] Sending launch variables to side panel", a), h.send("launcher/resolved_launch_variables", {    launchVariables: a  })}))

Readable extractor object from deobfuscated content.js

content.js
  const S = {    async getRehostedPdfSecurePath() {      const t = await fetch(""),        e = await t.blob(),        {          fields: n,          path: i,          url: s        } = await w.getSignedUploadUrl(),        a = new FormData;      return Object.keys(n).forEach((t => {        a.append(t, n[t])      })), a.append("file", e), await w.uploadFile(s, a), i    },    getSelectedContent() {      try {        const t = window.getSelection();        if (!t || !t.rangeCount) return null;        const e = document.activeElement;        if (e && ("TEXTAREA" === e.tagName || "INPUT" === e.tagName)) {          const {            selectionStart: t,            selectionEnd: n,            value: i          } = e;          return i.substring(t ?? 0, n ?? 0)        }        const n = t.getRangeAt(0),          i = document.createElement("div");        return i.appendChild(n.cloneContents()), i.innerHTML      } catch {        return null      }    },    cleanDOM() {      const t = document.body.cloneNode(!0);      return b(t), t.innerHTML    },    getCleanTextContent() {      const t = window.getSelection();      if (t) {        const e = document.createRange();        e.selectNode(document.body), t.removeAllRanges(), t.addRange(e);        const n = t.toString();        return t.removeAllRanges(), n      }      const e = document.body.cloneNode(!0);      return b(e), e.innerText    },    getMetadataBundle() {      const t = (t, e = "property") => {          const n = [...document.getElementsByTagName("meta")].find((n => n.getAttribute(e) === t));          return n ? n.content : null        },        e = {          title: t("og:title") || document.title,          description: t("og:description") || t("description", "name") || "",          openGraphImage: t("og:image") || "",          openGraphTags: [...document.getElementsByTagName("meta")].filter((t => t.getAttribute("property")?.startsWith("og:"))).reduce(((t, e) => {            const n = e.getAttribute("property");            return n && (t[n] = e.content), t          }), {}),          favicon: (() => {            const t = [...document.getElementsByTagName("link")].filter((t => t.getAttribute("rel") && t.getAttribute("rel")?.includes("icon")));            return t.sort(((t, e) => {              const n = parseInt(t.getAttribute("sizes")?.split("x")[0] || "0");              return parseInt(e.getAttribute("sizes")?.split("x")[0] || "0") - n            })), t.length > 0 ? t[0].href : null          })(),          brand: this.getBrandBundle()        };      return JSON.stringify(e, null, 2)    },    getBrandBundle() {      const t = t => {        const e = {};        return document.querySelectorAll("*").forEach((n => {          let i = getComputedStyle(n)[t];          i && "rgba(0, 0, 0, 0)" !== i && "transparent" !== i && ("color" !== t && "backgroundColor" !== t || (i = (t => {            const e = t.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);            if (!e) return t;            const n = parseInt(e[1], 10),              i = parseInt(e[2], 10),              s = parseInt(e[3], 10),              a = void 0 !== e[4] ? parseFloat(e[4]) : 1,              o = t => t.toString(16).padStart(2, "0"),              r = `#${o(n)}${o(i)}${o(s)}`;            return a < 1 ? `${r}${o(Math.round(255*a))}` : r          })(i)), e[i] = (e[i] || 0) + 1)        })), Object.entries(e).map((([t, e]) => ({          value: t,          count: e        }))).sort(((t, e) => e.count - t.count))      };      return {        fonts: t("fontFamily").slice(0, 5),        textColors: t("color").slice(0, 5),        backgroundColors: t("backgroundColor").slice(0, 5)      }    }  };

What it can do

Permissions this extension asks for, as declared in version 0.7.16. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Store data in your browser

    storage

  • Act on the current tab, but only after you click the extension

    activeTab

  • Show a panel beside the page

    sidePanel

  • Show you desktop notifications

    notifications

Updated 30 September 2026njommheeefdkhenodpfoflmeoampaggk