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.4.0Chrome Web Store
45Risk
Who publishes it

ShengChen 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.

Publisher
ShengChen Inc.
Declared legal entity
ShengChen Inc.
Registered contact
SHENGCHEN TECHNOLOGY LIMITED

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

Publishers can request a review.

Findings

Feedback form attaches your entire local storage without disclosure

MediumunwantedCWE-359Dynamic sandbox

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.

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.

What actually leaves your device when you click Submit

  • 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.

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-Type
application/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)

The code that does this

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

Readable version

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 St

deobfuscated/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)

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.

Where it sends data

Destinations our analysis observed AI Chat Exporter: Gemini to PDF, MD and more contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • api.ai-chat-exporter.com

    AI Chat Exporter: Gemini to PDF, MD and more sends data to api.ai-chat-exporter.com. No other extension we have analyzed sends data here.

  • phus.ai-chat-exporter.com

    AI Chat Exporter: Gemini to PDF, MD and more sends data to phus.ai-chat-exporter.com. No other extension we have analyzed sends data here.

  • PostHog

    AI Chat Exporter: Gemini to PDF, MD and more sends data to PostHog. Named as a recipient by 1 other extensions we have analyzed.

Updated 2 November 2026jfepajhaapfonhhfjmamediilplchakk