Is ChatGPT Reader/Transcriber: Free AI Text to Speech/Speech to Text (TTS/STT) safe?

Medium risk

GPT Reader intercepts the user's OpenAI session token on chatgpt.com and transmits account credentials to readeon.com.

The extension injects a script into chatgpt.com that hooks window.fetch and captures the OpenAI Bearer token from the /backend-api/me response. The token, along with the user's email, display name, OpenAI user ID, and profile picture URL, is stored in chrome.storage.sync (synced across all signed-in Chrome instances) and then sent to www.readeon.com for vendor authentication. Subsequent requests to readeon.com include a SHA-256 hash of the raw OpenAI access token as a header.

democraticdeveloperv2.6.5Chrome Web Store
49Risk
Who publishes it

democraticdeveloper - 8 other listings from the same operator, 1 of them carrying a finding

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

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.

gpt-reader.com
Also called by 3 other listings, including ChatGPT Reader & Transcriber: Free AI Text↔Speech
readeon.com
Also called by 7 other listings, including ChatGPT Reader & Transcriber: Free AI Text↔Speech, Claude Reader: Free AI Text to Speech (TTS), Google Reader: Free Natural AI Text to Speech, TTS

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

Publishers can request a review.

Findings

OpenAI session token captured on ChatGPT, copied to synced Chrome storage

MediumunwantedCWE-522Dynamic sandbox

On chatgpt.com signed in, the extension hooks page network calls, reads your live OpenAI session token from the profile request, and writes it raw to chrome.storage.sync, synced to every browser on the account.

What actually happens

You did this

You open chatgpt.com while signed in.

The page makes its normal request to /backend-api/me to load your profile.

The extension did this

The extension reads your OpenAI session token from that request and saves it into cloud-synced Chrome storage.

A wrapper around window.fetch pulls the Authorization bearer token and hands it to the extension, which writes it to chrome.storage.sync.

The code that does this

The fetch hook that reads the bearer token off ChatGPT's profile request

Readable version

index.tsx-DL347dhR.js — AUTH_RECEIVED handler writes the raw token to synced storage

assets/index.tsx-DL347dhR.js
// d = AUTH_RECEIVED listener: split off the 'Bearer ' prefix, keep the raw tokend = async f => {  const { detail: { accessToken: y, userId: p, userData: g } } = f;  if (y.includes("Bearer")) { u(y.split(" ")[1], p, g); return }  u(y, p, g)}// u = persist to cloud-synced storageconst u = async (f, y, p) => {  const g = await kM(f);  // SHA-256 of the token  chrome.storage.sync.set({    email: p.email,    name: p.name,    openaiId: p.id,    picture: p.picture,    accessToken: f,       // raw OpenAI JWT    hashAccessToken: g  })}

What's stored on your device

What landed in synced storage after one ChatGPT visit: your live session token, copied by Chrome to every browser on the account.

Location
chrome.storage.sync (cloud-synced across every browser on the same Google account)
Contents
{  "name": "<test-account-name> (test account)",  "email": "<test-account-email> (test account)",  "picture": "https://s.gravatar.com/avatar/...png",  "openaiId": "user-4q4dbryKyBZhex41AUB7leIp",  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9wZW5haS00YjVjIn0.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9hY2NvdW50X2lkIjoiYTNmMS4uLiIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci00cTRkYnJ5S3lCWmhleDQxQVVCN2xlSXAifSwiYXVkIjpbImh0dHBzOi8vYXBpLm9wZW5haS5jb20vdjEiXSwiY2xpZW50X2lkIjoiYXBwX1g4elk2dlcycFE5dFIzZEU3bksxakw1Z0giLCJleHAiOjE3ODE2MDk5MTZ9.<signature> (illustrative shape; the captured value was a ~1,989-char OpenAI JWT)",  "hashAccessToken": "433a6cd45c8d5690b7e40c2a00f69f391b0d56992243229c6e89e0bd1ada6b09"}

Check if you're affected

Reads chrome.storage.sync and reports whether a raw OpenAI access token is present, and decodes the JWT header/payload so you can confirm it is a live OpenAI session credential.

Requires
  • A browser context with access to chrome.storage.sync for this extension
check-synced-token.js · js
// Paste into the extension's service-worker / a page console with chrome.storage access.chrome.storage.sync.get(['accessToken', 'hashAccessToken', 'email', 'openaiId'], (s) => {  const t = s.accessToken;  if (!t) { console.log('No accessToken in synced storage.'); return; }  console.log('accessToken length:', t.length);  try {    const [h, p] = t.split('.');    const dec = (b) => JSON.parse(atob(b.replace(/-/g, '+').replace(/_/g, '/')));    console.log('JWT header:', dec(h));    console.log('JWT payload:', dec(p));    console.log('aud (should include api.openai.com):', dec(p).aud);  } catch (e) { console.log('Value is not a decodable JWT:', e.message); }  console.log('hashAccessToken (SHA-256 of token):', s.hashAccessToken);});
How to run it
  1. 1Sign in to chatgpt.com; load once.
  2. 2Open the extension's storage context (service worker console).
  3. 3Paste and run this script.
  4. 4A printed JWT with aud including api.openai.com confirms the token is in synced storage.

ChatGPT profile data sent to vendor server readeon.com for authentication

MediumunwantedCWE-359Dynamic sandbox

After reading your ChatGPT profile, the extension POSTs your email, name and OpenAI ID to readeon.com for a session token; the token embeds that data.

What actually happens

You did this

You open chatgpt.com while signed in.

The extension already holds your profile fields from ChatGPT's /backend-api/me response.

The extension did this

The extension sends your email, name and OpenAI user ID to its vendor server readeon.com to get a sign-in token.

The POST goes to www.readeon.com/api/auth/token with an X-From-Extension: true header; the returned token is cached and reused.

Profile fields placed in the outbound POST body to www.readeon.com/api/auth/token

  • Your email address
    <test-account-email>

    The email on your OpenAI/ChatGPT account, which directly identifies you.

  • Your display name
    <test-account-name>

    The name shown on your ChatGPT account.

  • Your OpenAI user ID
    user-4q4dbryKyBZhex41AUB7leIp

    A stable identifier OpenAI assigns to your account, used to tie activity back to you.

Captured request

POSThttps://www.readeon.com/api/auth/token

Observed during dynamic analysis: 200 with a vendor JWT {token:...}. The token's base64-decoded payload was {email:<test-account-email>, name:<test-account-name>, openaiId:user-4q4dbryKyBZhex41AUB7leIp, ext:true, iat:..., exp:...}; the server could only embed those three fields if it received them, confirming readeon.com receives the email, name and OpenAI user ID. (The email/name/openaiId in the body above are the signed-in test-account values reflected back in the server-issued token.)

Headers
Content-Type
application/json
X-From-Extension
true
Body
{  "email": "<test-account-email>",  "name": "<test-account-name>",  "openaiId": "user-4q4dbryKyBZhex41AUB7leIp"}

The code that does this

The vendor auth POST and the per-request Hat-Token hash of the OpenAI token

Readable version

utils-pzkA9eIg.js — Cte() reads profile from synced storage, POSTs to readeon, caches the vendor token

assets/utils-pzkA9eIg.js
async function Cte() {  const { jwtToken: t, jwtTokenExpiry: e } = await chrome.storage.local.get(["jwtToken","jwtTokenExpiry"]);  const r = await new Promise((c) => chrome.storage.sync.get(["email","name","openaiId"], c));  const { email: n, name: i, openaiId: a } = r, o = Date.now();  if (t && e && o < e - l7) return t;            // reuse cached vendor token  const s = await fetch(`${Us}/auth/token`, {      // Us = 'https://www.readeon.com/api'    method: "POST",    headers: { "X-From-Extension": "true", "Content-Type": "application/json" },    body: JSON.stringify({ email: n, name: i, openaiId: a })  });  const { token: l } = await s.json();  await chrome.storage.local.set({ jwtToken: l, jwtTokenExpiry: o + c7 });  return l;}

utils-pzkA9eIg.js — Tu(): every readeon API call also sends Hat-Token = SHA-256 of the OpenAI access token

assets/utils-pzkA9eIg.js
async function Tu(t, e = {}) {  const i = await Cte();                 // vendor bearer token  const a = await bte("hashAccessToken"); // SHA-256 of raw OpenAI access token  const o = await fetch(t, { ...e, headers: {    ...e.headers,    Authorization: `Bearer ${i}`,    "Hat-Token": a,    "Content-Type": "application/json",    "X-From-Extension": "true"  }});  return o.json();}

Where the profile data goes

    • www.readeon.com

    Vendor backend (GPT Reader/readeon). Receives email, name and OpenAI user ID at /api/auth/token, issues a session token, and gets a SHA-256 hash of your access token via Hat-Token.

What it can do

Permissions this extension asks for, as declared in version 2.5.2. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2.6.5, which we have not unpacked yet.

  • See the address and title of every tab you have open

    tabs

  • Store data in your browser

    storage

  • Show you desktop notifications

    notifications

  • Act on the current tab, but only after you click the extension

    activeTab

  • Add items to the right-click menu

    contextMenus

  • Run its own code inside the pages you visit

    scripting

Where it sends data

Destinations our analysis observed ChatGPT Reader/Transcriber: Free AI Text to Speech/Speech to Text (TTS/STT) 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.

  • www.readeon.com

    ChatGPT Reader/Transcriber: Free AI Text to Speech/Speech to Text (TTS/STT) sends data to www.readeon.com. One other extension we have analyzed sends data here.

Updated 2 November 2026aeggkceabpfajnglgaeadofdmeboimml