Is AI Exporter: Save ChatGPT, Gemini to PDF, Word, Markdown and Notion safe?

Medium risk

AI Exporter: Save ChatGPT, Gemini to PDF, Word, Markdown and Notion is medium risk. AI Exporter's error reporter sends data only when a dev-server setting allows it. We confirmed it was on, then triggered an export failure on chatgpt.com via the export button.…

Colinv4.4.6Chrome Web Store
45Risk
Who publishes it

Colin - no other listings under this identity, 2 shared hostnames

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

Publisher
Colin
Website

Shared hosts - 2 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

api-service.saveai.net
Also called by 2 other listings, including AI Exporter - Gemini to PDF & Sync to Notion
saveai.net
Also called by 2 other listings, including AI Exporter - Gemini to PDF & Sync to Notion

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

Publishers can request a review.

Findings

Error Reports Send Page URL And Persistent ID To Sentry

AI Exporter's error reporter sends data only when a dev-server setting allows it.

We confirmed it was on, then triggered an export failure on chatgpt.com via the export button.

It reported the page URL and a persistent local ID to Sentry.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You try to export an AI chat (Markdown, PDF, or the ChatGPT API) and the export fails.

Failures happen for ordinary reasons such as an empty conversation, an expired login, or a blocked network request.

The extension did this

The extension sends an error report to Sentry that includes the full URL of the page you're on and a persistent client ID stored on your device.

The report only goes out if a setting fetched from the developer's own server allows it; if that fetch itself fails, reporting defaults to on.

Captured request
GEThttps://cdn-conf.saveai.net/saveai-ext.json

{ "sentry": { "open": true, "dsn": "https://4053611ccf153fc02bfaa60038c45d7c@o4511456612450304.ingest.us.sentry.io/4511529477079040" } }

Captured request
POSThttps://o4511456612450304.ingest.us.sentry.io/api/4511529477079040/envelope/?sentry_version=7&sentry_key=4053611ccf153fc02bfaa60038c45d7c&sentry_client=sentry.javascript.browser%2F10.56.0

200 OK, empty body (standard Sentry ingest acknowledgement).

Body
{"event_id":"4242356c687f48f29426f99a5b0960a4","sent_at":"2026-08-16T09:11:18.149Z","sdk":{"name":"sentry.javascript.browser","version":"10.56.0"},"trace":{"environment":"production","release":"ai-exporter@4.3.9-chrome+34f847e","public_key":"4053611ccf153fc02bfaa60038c45d7c","trace_id":"6b398aa793f044c2a99e1e0062da99ec","org_id":"4511456612450304"}}{"type":"event"}{"exception":{"values":[{"type":"Error","value":"无法从URL中提取对话ID","stacktrace":{"frames":[{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"?","in_app":true,"lineno":844,"colno":1583},{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"MN","in_app":true,"lineno":595,"colno":5959},{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"Zu","in_app":true,"lineno":595,"colno":4707},{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"Ep","in_app":true,"lineno":272,"colno":14019},{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"Object.jX [as getAllChatsForTopic]","in_app":true,"lineno":222,"colno":6078},{"filename":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","function":"xD","in_app":true,"lineno":218,"colno":14847}]},"mechanism":{"type":"generic","handled":true}}]},"level":"error","event_id":"4242356c687f48f29426f99a5b0960a4","platform":"javascript","timestamp":1786871478.147,"environment":"production","release":"ai-exporter@4.3.9-chrome+34f847e","tags":{"extension_context":"content-script","browser":"chrome","extension_version":"4.3.9","feature":"chatgpt_api","action":"get_all_chats_for_topic","error_category":"unknown","ai_site":"chatgpt","report_layer":"chatgpt_api"},"user":{"id":"C6uDJywwDxBQyLQY7tHQmrbPmAkUnY48"},"contexts":{"trace":{"trace_id":"6b398aa793f044c2a99e1e0062da99ec","span_id":"86f4e69e3b5166cb"},"page":{"url":"https://chatgpt.com/","browserLanguage":"en-US"},"saveai":{"feature":"chatgpt_api","action":"get_all_chats_for_topic","errorCategory":"unknown","elapsedMs":2,"includeAllBranches":false}},"debug_meta":{"images":[{"type":"sourcemap","code_file":"chrome-extension://jeboalooabaicnhmedphfipgfjdobbdi/content-scripts/content.js","debug_id":"af9d4b3a-789c-4317-98d5-9c417da3da8b"}]},"sdk":{"name":"sentry.javascript.browser","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/browser","version":"10.56.0"}],"settings":{"infer_ip":"never"}}}
The code that does this

The remote-config gate that decides whether error reports leave the device

Readable version

Remote-config gate (isRemoteEnabled / loadRemoteEnabled)

content-scripts/content.js
  static async isRemoteEnabled() {    if (this.remoteConfigCache && this.isRemoteConfigFresh(this.remoteConfigCache)) return this.remoteConfigCache.enabled;    if (this.remoteConfigPromise) return this.remoteConfigPromise;    this.remoteConfigPromise = this.loadRemoteEnabled();    try {      return await this.remoteConfigPromise    } finally {      this.remoteConfigPromise = null    }  }  static async loadRemoteEnabled() {    const t = await this.getRemoteConfigCache();    if (t && this.isRemoteConfigFresh(t)) return this.remoteConfigCache = t, t.enabled;    let n = t?.enabled ?? !0;    const r = new AbortController,      i = setTimeout(() => r.abort(), wX);    try {      const s = await fetch(vX, {        cache: "no-store",        signal: r.signal      });      if (!s.ok) throw new Error(`HTTP ${s.status}`);      const u = await s.json();      if (typeof u.sentry?.open != "boolean") throw new Error("Invalid sentry.open value");      n = u.sentry.open    } catch {} finally {      clearTimeout(i)    }    const a = {      enabled: n,      fetchedAt: At().valueOf()    };    return this.remoteConfigCache = a, await this.setRemoteConfigCache(a), n  }

URL sanitizer used before setting page context

content-scripts/content.js
    zG = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,    BG = /[0-9a-f]{16,}/gi,    dp = e => e.replace(zG, "***").replace(BG, "***"),
What a single Sentry report contains
  • Page URL
    https://chatgpt.com/

    The exact web address you were on when the error happened.

  • Persistent client ID
    C6uDJywwDxBQyLQY7tHQmrbPmAkUnY48

    A per-install ID generated once and reused on every report, letting Sentry link them together over time.

  • Extension version
    ai-exporter@4.3.9-chrome+34f847e

    Which build of the extension sent the report.

  • Failing feature/action
    feature=chatgpt_api, action=get_all_chats_for_topic

    Which feature and step failed, e.g. loading a ChatGPT conversation or exporting to Markdown.

  • Browser language
    en-US

    Your browser's configured display language.

Where the report goes
    • o4511456612450304.ingest.us.sentry.io

    Sentry ingest endpoint for this extension's error-monitoring project, operated by Sentry (Functional Software, Inc.), a third-party error-tracking vendor.

    • cdn-conf.saveai.net

    The developer's own remote-config endpoint that turns Sentry reporting on or off for installed copies of the extension.

Updated 30 September 2026kagjkiiecagemklhmhkabbalfpbianbe