Is AI Chat Exporter: Gemini to PDF, MD and more safe?
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.
Who publishes itShengChen Inc. - no other listings under this identity
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.
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
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.
What actually happens
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 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 messageThe 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 emailJordan 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 IDscusId: 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 extensioncached 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
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.
- Content-Type
- application/json
{"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 feedback handler (function St)
deobfuscated/background.jsfunction 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.jschrome.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.