Is ChatGenie for Chatgpt safe?
ChatGenie is critical risk. ChatGenie reads every message you send ChatGPT and every reply it gives, then forwards the full text to a server the developer controls, along with a persistent per-device ID. We confirmed this with a planted test prompt.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
ChatGenie sends every ChatGPT prompt and reply to a third-party server
ChatGenie reads every message you send ChatGPT and every reply it gives, then forwards the full text to a server the developer controls, along with a persistent per-device ID.
We confirmed this with a planted test prompt.
You type a message to ChatGPT on chatgpt.com and wait for its reply to finish streaming.
No extension UI action is needed; this happens on ordinary use of chatgpt.com.
The extension reconstructs your full message and ChatGPT's full reply from the network response, then sends both to a server it controls.
A persistent per-device ID is attached to the request.
Fetch override captures the conversation, then the service worker POSTs it out
function installFetchOverride() {
if (!location.href.toLowerCase().startsWith('https://chatgpt.com/')) return false;
const { fetch: originalFetch } = window;
function isConversationRequest(url) {
try {
return new URL(url).pathname.endsWith('/conversation');
} catch {
return false;
}
}
window.fetch = async (...args) => {
const [request, init] = args;
const url = request.url || request.toString();
const response = await originalFetch(...args);
const contentType = response.headers.get('content-type') || '';
if (!isConversationRequest(url) || !contentType.includes('text/event-stream')) {
return response;
}
let requestBody;
try { requestBody = JSON.parse(init.body); } catch {}
let conversationId = requestBody.conversation_id;
const outgoingMessages = requestBody.messages;
const { readable, writable } = new TransformStream();
if (response.body) {
const reader = response.body.getReader();
const writer = writable.getWriter();
const decoder = new TextDecoder();
let buffer = '', replyText = '', replyMessageId = '', model = '';
(async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let boundary;
while ((boundary = buffer.indexOf('\n\n')) !== -1) {
const rawEvent = buffer.slice(0, boundary).trim();
buffer = buffer.slice(boundary + 2);
if (!rawEvent) continue;
const evt = parseSSE(rawEvent);
if (evt.event === 'delta' || evt.event === 'message') {
let data;
try { data = JSON.parse(evt.data); } catch {}
if (data) {
if (typeof data.v === 'string') {
replyText += data.v;
} else if (data.v && data.v.length > 0) {
for (const op of data.v) {
if (op.v && op.o === 'append' && typeof op.v === 'string') replyText += op.v;
}
} else if (data.v && data.v.message && data.v.message.id) {
replyMessageId = data.v.message.id;
} else if (data.metadata) {
if (data.conversation_id) conversationId = data.conversation_id;
if (data.metadata.model_slug) model = data.metadata.model_slug;
}
}
}
writer.write(value);
}
}
writer.close();
for (const msg of outgoingMessages || []) {
if (msg.content && msg.content.parts && msg.content.parts.length > 0) {
postToPage({
client_ts: Date.now(), session_id: conversationId, message_id: msg.id,
chat_domain: 'chatgpt.com', model, prompt: msg.content.parts.join(''),
role: 'user', is_subscribed: false, language: ''
});
}
}
postToPage({
client_ts: Date.now(), session_id: conversationId, message_id: replyMessageId,
chat_domain: 'chatgpt.com', model, prompt: replyText,
role: 'system', is_subscribed: false, language: ''
});
})();
return new Response(readable, { headers: response.headers, status: response.status, statusText: response.statusText });
}
return response;
};
return true;
};
function postToPage(payload) {
window.postMessage({ source: 'content-script', payload });
}
function parseSSE(rawEvent) {
const lines = rawEvent.split(/\r?\n/);
const evt = { event: 'message', data: '', id: '', retry: '' };
for (const line of lines) {
if (line.startsWith(':')) continue;
const [field, ...rest] = line.split(':');
const value = rest.join(':').trimStart();
if (field === 'event') evt.event = value;
else if (field === 'data') evt.data += value + '\n';
else if (field === 'id') evt.id = value;
else if (field === 'retry') evt.retry = value;
}
if (evt.data.endsWith('\n')) evt.data = evt.data.slice(0, -1);
return evt;
}const MSG_TYPE = 'leak_prevention_msg';
function relayToBackground() {
if (window.top !== window) return false; // only run in the top frame
return window.addEventListener('message', (event) => {
if (event.source !== window || !event.data || event.data.source !== 'content-script') return;
chrome.runtime.sendMessage({ type: MSG_TYPE, data: event.data.payload });
}), true;
}const MSG_TYPE = 'leak_prevention_msg';
const LEAK_PREVENTION_ENDPOINT = 'https://api.chatgpt-chrome.com/api/leakprevention/v2/check';
function showSensitiveWarning() {
alert("Your prompt may have sensitive information inside. This message won't be shown again for this tab.");
}
async function submitForScreening(payload, tabId) {
payload.cid = await Analytics.getOrCreateClientId(); // persistent per-device UUID, reused forever
const response = await fetch(LEAK_PREVENTION_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.ok) {
const result = await response.json();
if (result.verdict === 'unsafe' && tabId) {
const shownKey = `shown_${tabId}`;
const alreadyShown = await chrome.storage.session.get([shownKey]);
if (!alreadyShown[shownKey]) {
await chrome.storage.session.set({ [shownKey]: true });
await chrome.scripting.executeScript({ target: { tabId }, func: showSensitiveWarning });
}
}
}
}
function initLeakPreventionListener() {
chrome.runtime.onMessage.addListener((message, sender) => {
if (message && message.type === MSG_TYPE) {
submitForScreening(message.data, sender.tab && sender.tab.id);
}
});
return true;
}| Content-Type | application/json |
{
"client_ts": 1725389421000,
"session_id": "68a1e4c2-1f09-4b7a-9c3e-2d6f8a1b7c44",
"message_id": "a3f7e912-88c4-4a1b-9e02-7d5c1f3a9b60",
"chat_domain": "chatgpt.com",
"model": "gpt-4o",
"prompt": "What are the side effects of ibuprofen? (illustrative)",
"role": "user",
"is_subscribed": false,
"language": "",
"cid": "4e1a9f7c-2b83-4c56-8e70-1a9d3f6b7c02"
}| Field | Value | Why it matters | |
|---|---|---|---|
Your message text | What are the side effects of ibuprofen? | The full text you typed to ChatGPT, unmodified. | |
ChatGPT's reply text | Common side effects of ibuprofen include stomach upset... | The full text of the AI's response to your message. | |
Device ID (cid) | 4e1a9f7c-2b83-4c56-8e70-1a9d3f6b7c02 | A UUID stored in chrome.storage.local and reused indefinitely, tying every submission back to the same device. | |
Conversation ID | 68a1e4c2-1f09-4b7a-9c3e-2d6f8a1b7c44 | ChatGPT's own conversation identifier, letting the receiving server group your messages by conversation. | |
Model used | gpt-4o | Which ChatGPT model produced the answer. |
- api.chatgpt-chrome.com
Receives the full prompt and AI reply text plus the persistent device ID on every ChatGPT conversation turn. Operated by ChatGenie's developer, not OpenAI.