Is AI Chat Exporter: Gemini to PDF, MD and more safe?

Medium risk

AI Chat Exporter sends the full text of exported Gemini conversations, plus your Google account name and email, to the vendor's servers.

When you export a Gemini chat, the extension packs the entire conversation (every message turn) along with the account holder's name and email and POSTs it to the vendor's backend at api.ai-chat-exporter.com to render the PDF server-side. The same inbound message, including the conversation body and your name/email, is also attached as a property on a PostHog analytics event and forwarded through the vendor's analytics proxy at phus.ai-chat-exporter.com. The extension only runs on gemini.google.com, but the conversation content leaves your browser whenever you trigger an export.

ShengChen Inc.v4.3.0Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Feedback form attaches your entire local storage without disclosure

Submitting the Feedback dialog makes the background read every local-storage key and attach it to the vendor report, including your account name, email, and internal IDs, even with email left blank.

A planted storage marker confirmed this.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the extension's Feedback dialog, type a short description, and leave the optional email field blank before clicking Submit.

This is the only user-facing form field for a name or email in the feedback flow.

The extension did this

The background script reads every key in the extension's local storage and attaches the whole dump to the report before sending it to the vendor's backend.

This includes your signed-in account's name and email and internal account/analytics identifiers, none of which appear anywhere in the visible feedback form.

02EvidenceFIELD TABLE
What actually leaves your device when you click Submit
FieldValueWhy it matters
Your typed feedback message
The PDF export sometimes fails on long conversations. (illustrative)The description you write in the dialog is sent as part of the report, as you'd expect.
Signed-in account name and email
Jordan Lee <jordan.lee@example.com> (illustrative)Even with the visible email field left blank, your account's name and email are pulled from local storage and attached anyway.
Account and analytics client IDs
cusId: u_8f2c1a90, analytics client ID: 1823456789.1700000000 (illustrative)A persistent account ID and analytics client ID travel with the report, letting it be linked to your other activity with this extension.
Everything else cached by the extension
cached account-info and usage records (illustrative)Any other data the extension has stored locally is included wholesale, whether or not it has anything to do with your feedback.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://apir.ai-chat-exporter.com/api/feedback/v1
Observed during dynamic analysis: with a planted marker value seeded into the extension's local storage beforehand and the visible email field left blank, the outgoing feedback POST's 'storage' field still carried the planted account name/email, confirming the data leaves the device regardless of what the visible form contains. The endpoint returned 200 OK.
Headers
Content-Typeapplication/json
Body
{"type":"bug","description":"The PDF export sometimes fails on long conversations.","email":"","screenshotUrls":[],"context":{},"errorInfo":null,"metaData":{},"ext":"AI Chat Exporter","cid":"1823456789.1700000000","version":"4.0.0","storage":{"session":{"user":{"cusId":"u_8f2c1a90","given_name":"Jordan","family_name":"Lee","email":"jordan.lee@example.com"}},"ga.clientid":"1823456789.1700000000"}} (illustrative reconstruction of the captured request shape)
04EvidenceCODE COMPARE
The code that does this

The feedback handler reads all of local storage and attaches it to the request body

What it actually does
Readable feedback handler (function St)deobfuscated/background.js
function St() {
  return (St = o(a().mark((function e(t, r, n) {
    var o, s, i, c, l, u;
    return a().wrap((function(e) {
      for (;;) switch (e.prev = e.next) {
        case 0:
          return o = { errcode: -1, errmsg: "unknown error", rslt: null }, e.prev = 1, e.next = 4, Ue();
        case 4:
          return s = e.sent, i = chrome.runtime.getManifest().version, e.next = 8, chrome.storage.local.get(null);
        case 8:
          return c = e.sent, l = Ot(Ot({}, t.data), {}, {
            ext: d.ext.name,
            cid: s,
            version: i,
            storage: c
          }), v.debug("fetch feedback API, body keys:", Object.keys(l)), e.next = 13, fetch(d.backend.feedback, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(l)
          });
        case 13:
          if ((u = e.sent).ok) { e.next = 18; break }
          o = { errcode: -2, errmsg: "API error: ".concat(u.status, " ").concat(u.statusText), rslt: null }, e.next = 21;
          break;
        case 18:
          return e.next = 20, u.json();
        case 20:
          o = e.sent;
        case 21:
          e.next = 27;
          break;
        case 23:
          e.prev = 23, e.t0 = e.catch(1), v.error("handleOnMsgFeedback error:", e.t0), o = { errcode: -3, errmsg: e.t0.message, rslt: null };
        case 27:
          v.debug("onMessage[feedback] sendRsp errcode:", o.errcode), n(o), Xe("Background Runtime Message", {
            extname: d.ext.name,
            status: "end",
            cmd: "feedback",
            rsp: o
          });
        case 30:
        case "end":
          return e.stop()
      }
    }), e, null, [[1, 23]])
  }))).apply(this, arguments)
}
Readable message router dispatching cmd==='feedback' to Stdeobfuscated/background.js
chrome.runtime.onMessage.addListener((function(e, t, r) {
  v.debug("in handleRuntimeOnMessage, req:", e), v.debug("in handleRuntimeOnMessage, sender:", t), Xe("Background Runtime Message", {
    status: "begin",
    cmd: e.cmd,
    req: e
  });
  var n = null;
  if ("open_options_page" === e.cmd) chrome.runtime.openOptionsPage(), r({ errcode: 0, errmsg: "options page opened" });
  else {
    if ("pdf" === e.cmd) return function(e, t, r) { ut.apply(this, arguments) }(e, t, r), !0;
    if ("account" === e.cmd) return ft(e, t, r), !0;
    if ("usage" === e.cmd) return yt(e, t, r), !0;
    if ("atft" === e.cmd) return wt(e, t, r), !0;
    if ("feedback" === e.cmd) return function(e, t, r) { St.apply(this, arguments) }(e, t, r), !0;
    if ("image_fetch" === e.cmd || "fetch_and_upload_image" === e.cmd) return function(e, t, r) { Tt.apply(this, arguments) }(e, t, r), !0;
    v.error("wrong req.msg"), r(n = { errcode: -1, errmsg: "wrong req.msg" })
  }
  v.debug("rsp:", n)
})), chrome.runtime.onMessageExternal.addListener(Ct)
05EvidenceTHIRD PARTY LIST
Where the feedback report, and everything attached to it, is sent
  • apir.ai-chat-exporter.com

    The extension vendor's own API backend; receives the feedback report including the full local-storage attachment.

Data recipients

api.ai-chat-exporter.comphus.ai-chat-exporter.comPostHog
Updated 17 September 2026jfepajhaapfonhhfjmamediilplchakk