Is ChatGPT to PDF safe?
ChatGPT to PDF is high risk. ChatGPT PDF export's remote-generation path builds a JSON payload from page HTML, message, title, settings, user info, then POSTs to chatgpt-to-pdf-nodejs-production.up.railway.app. No live request was captured; evidence is the code path.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
ChatGPT PDF exports can post conversation HTML to Railway
ChatGPT PDF export's remote-generation path builds a JSON payload from page HTML, message, title, settings, user info, then POSTs to chatgpt-to-pdf-nodejs-production.up.railway.app.
No live request was captured; evidence is the code path.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You start the extension PDF export on a ChatGPT conversation.
The remote request path is used for qualifying exports where the generated HTML is below the code threshold and does not match the image condition.
The extension packages the conversation content and account context for a remote PDF-generation request.
The background script sends the JSON to the Railway-hosted PDF service with an extension-generated authorization token.
- Conversation titleQ3 incident response planning (illustrative)
Names the ChatGPT conversation being exported.
- Conversation HTML<div class="group/turn-messages">Discuss the SSO rollout timeline</div> (illustrative)
Contains the rendered ChatGPT conversation content and page formatting selected for the PDF.
- Message text recordstextdoc-message-0: <div class="cm-line">Roll out to admins first</div> (illustrative)
Includes text-document message fragments collected from the ChatGPT page.
- User informationpatricia.nguyen@example.com, paid=true (illustrative)
Can attach account identifiers and an email address to the exported conversation.
- Export metricspdfPrintType=full, font=default, fontSize=normal (from code fields)
Records how the PDF was produced and which display settings were selected.
No live response was captured during dynamic analysis; the source code constructs this POST when the remote PDF path is used.
The content script builds the export data and the background script posts it
Readable background POST construction
deobfuscated/background.js }(bo().m(function t(e, r, i, n) { var s, a, o, u, l, h, c, d, f, m, _, y; return bo().w(function(t) { for (;;) switch (t.n) { case 0: return t.n = 1, b(); case 1: return s = t.v, a = chrome.i18n.getUILanguage(), t.n = 2, v(); case 2: return _o(o = t.v.toString(), "startFetchPdf", { paywallUserId: i.id, email: i.email }), u = performance.now(), t.n = 3, fetch("https://chatgpt-to-pdf-nodejs-production.up.railway.app/generate-pdf-from-html", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + s, "accept-Language": a }, body: JSON.stringify({ title: e.title, html: e.html, textDocMessages: e.textDocMessages, theme: r.theme, font: r.font, fontSize: r.fontSize, showWatermark: r.showWatermark, userInfo: i, metricInfo: n }) }); case 3: if (l = t.v, h = performance.now() - u, _o(o, "endFetchPdf", { code: l.status, paywallUserId: i.id, email: i.email, latency: h }), l.ok) { t.n = 5; break } return t.n = 4, l.text(); case 4: throw c = t.v, 404 === l.status ? (C(), c = "The requested server was not found. Please try again later.") : l.status >= 500 && (C(), c = "There's a problem with our server. Please try again later."), new Error(c); case 5: return d = Eo(e.title), f = "".concat(d, ".pdf"), t.n = 6, l.blob(); case 6: return m = t.v, t.n = 7, p(m); case 7: return _ = t.v, y = "data:application/pdf;base64,".concat(_), t.n = 8, g(y, f); case 8: I(); case 9: return t.a(2) } }, t) })), wo.apply(this, arguments)Readable content-script export payload
deobfuscated/contentScript.js return e.n = 18, (0, s.B)(t, i); case 18: p = e.v, f = p.title, g = p.html, v = p.textDocMessages, _ = "full", i && (_ = "selected"), t && (_ = "aiTextOnly"), m = { title: f, html: g, textDocMessages: Object.fromEntries(v) }, y = { theme: F, font: O, fontSize: L, showWatermark: yh }, b = 0, w = vh(v.values()); try { for (w.s(); !(x = w.n()).done;) { k = x.value, S = vh(k); try { for (S.s(); !(P = S.n()).done;) T = P.value, b += T.length } catch (e) { S.e(e) } finally { S.f() } } } catch (e) { w.e(e) } finally { w.f() } if (!(Math.floor((g.length + f.length + b) / 1024) < 2e3) || (0, s.M)(g)) { e.n = 19; break } chrome.runtime.sendMessage({ action: r.bL, data: { contentData: m, params: y, userInfo: bh, metricInfo: { pdfPrintType: _, font: O, fontSize: L } } }, function(e) { A(), e && e.error ? (0, n.D)(e.error) : (R && z(), o(function(e) { e && l() })) }), e.n = 23;- chatgpt-to-pdf-nodejs-production.up.railway.app
Railway-hosted PDF generation endpoint that receives the export JSON built from the ChatGPT page.
PostHog Analytics Sends User Email and Subscription Status on Every PDF Export
Each time you export a ChatGPT conversation to PDF, the extension sends your registered email, account ID, and subscription tier to PostHog's EU analytics servers, before and after generation, tying your identity to every export.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You export a ChatGPT conversation to PDF.
The export action is the sole trigger, no additional interaction is required.
The extension sends your email address and account ID to PostHog analytics on eu.i.posthog.com, both before and after the PDF generation request completes.
The 'startFetchPdf' event carries your email and paywall user ID. The 'endFetchPdf' event adds the HTTP status code and request latency.
PostHog initialization and identity binding (background.js, lines 16465-16470, 16728-16729)
// background.js lines 16465–16470function sendAnalyticsEvent(userId, eventName, properties) { identifyUser(userId); posthog.capture(eventName, properties);}function identifyUser(userId) { posthog.identify(userId); // links all future events to this user's person profile}// background.js lines 16728–16729posthog.init('phc_QsGf4ScUul0ZSUq6uWGb3xOLtegD6pGD4rTI0Scz8B8', { api_host: 'https://eu.i.posthog.com'});- Your email addressjane.smith@example.com
Your registered account email is sent with each export event, permanently linking the action to your identity in the developer's analytics.
- Paywall user IDusr_7f3a29bc1d44e8a2
A persistent ID tied to your subscription account, used as the PostHog person identifier across all sessions.
- Event nameendFetchPdf
Labels the action you took ('startFetchPdf' or 'endFetchPdf'), building a behavioral timeline in the developer's PostHog dashboard.
- Request latency1823
The time in milliseconds the PDF server took to respond, included in the end event.
- HTTP status code200
The server response code, indicating whether the export succeeded or failed.
- eu.i.posthog.com
PostHog analytics (EU region). Receives user email, account ID, and export event data. Retention is subject to PostHog's and the developer's data policies.
What it can do
Permissions this extension asks for, as declared in version 1.3.12. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on chatgpt.com
*://chatgpt.com/*
Act on the current tab, but only after you click the extension
activeTab
Start, monitor and manage your downloads
downloads
Store data in your browser
storage