Is Voice Control for ChatGPT safe?
Voice Control for ChatGPT sends ChatGPT conversation URLs and page titles to Google Analytics with a persistent user identifier.
Each time you navigate within ChatGPT, the extension captures the page title and conversation URL (which contains a unique conversation GUID) and transmits them to Google Analytics via the GA4 Measurement Protocol. A persistent client ID generated at install is attached to every event, allowing all your ChatGPT browsing activity to be linked across sessions.
Who publishes itAidia ApS - 1 other listing from the same operator, none carrying a finding
Aidia ApS - 1 other listing from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
1 other listing published from this account, 3k+ users between them, none of them carrying a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
ChatGPT Conversation URLs Sent to Google Analytics
Each time you open or switch ChatGPT conversations, this extension sends the URL and page title to Google Analytics.
Dynamic analysis confirmed repeated requests, each carrying a persistent install ID, linking your visits over time.
You open or navigate between conversations on ChatGPT.
This includes loading chatgpt.com, opening a new chat, or switching to an existing conversation.
The extension sends the conversation URL and page title to Google Analytics, along with a persistent identifier unique to your browser installation.
A session identifier that groups navigations within one browsing period is also included.
| Field | Value | Why it matters | |
|---|---|---|---|
Persistent installation ID | 6df0f8d6-147d-4b59-a138-1e5c83e730bf | A unique ID generated at install, kept permanently. Every analytics request carries it, linking your ChatGPT visits across sessions. | |
Session ID | 1719530400000 | A timestamp-based value that groups your navigations within one browsing period. Resets after 30 minutes of inactivity. | |
Conversation URL | https://chatgpt.com/c/7a3f9c21-1d4e-4b8a-9f2d-3e8c5b6d1a04 | The full address of your ChatGPT conversation, including its unique ID. The URL path reveals which conversation you opened. | |
Page title | How to configure AWS S3 bucket policies — ChatGPT | The title of the ChatGPT page at the time of navigation, which often reflects the topic or opening message of a conversation. |
| Content-Type | text/plain;charset=UTF-8 |
{
"client_id": "6df0f8d6-147d-4b59-a138-1e5c83e730bf",
"events": [
[
{
"name": "page_view",
"params": {
"page_title": "How to configure AWS S3 bucket policies — ChatGPT",
"page_location": "https://chatgpt.com/c/7a3f9c21-1d4e-4b8a-9f2d-3e8c5b6d1a04",
"client_id": "6df0f8d6-147d-4b59-a138-1e5c83e730bf",
"session_id": "1719530400000",
"engagement_time_msec": 100
}
}
]
]
}Background worker: persistent client ID and GA4 Measurement Protocol dispatch
// On first run, generate a UUID and persist it in local storage.
// All subsequent calls return the same UUID, creating a durable cross-session identity.
async function getOrCreateClientId() {
let clientId = (await chrome.storage.local.get('clientId')).clientId;
if (!clientId) {
clientId = self.crypto.randomUUID();
await chrome.storage.local.set({ clientId });
}
return clientId;
}const SESSION_TIMEOUT_MINUTES = 30;
async function getOrCreateSessionId() {
let { sessionData } = await chrome.storage.session.get('sessionData');
const now = Date.now();
if (sessionData?.timestamp) {
const minutesElapsed = (now - sessionData.timestamp) / 60000;
if (minutesElapsed > SESSION_TIMEOUT_MINUTES) {
sessionData = null; // session expired, create new one
} else {
sessionData.timestamp = now; // refresh session TTL
await chrome.storage.session.set({ sessionData });
}
}
if (!sessionData) {
sessionData = { session_id: now.toString(), timestamp: now.toString() };
await chrome.storage.session.set({ sessionData });
}
return sessionData.session_id;
}const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect';
const MEASUREMENT_ID = 'G-67EVHBE3DC';
const API_SECRET = '<redacted>'; // GA4 Measurement Protocol api_secret — value redacted
const ENGAGEMENT_TIME_MS = 100;
let eventQueue = [];
let lastFlushTime = Date.now();
async function flushEventQueue() {
if (eventQueue.length === 0) return;
lastFlushTime = Date.now();
const events = eventQueue; // capture current queue
eventQueue = []; // reset before async work
const clientId = await getOrCreateClientId();
await fetch(
`${GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
{
method: 'POST',
body: JSON.stringify({ client_id: clientId, events: [events] }),
}
);
}// Queue an event, flushing immediately or after a 2-second debounce.
function queueEvent(event) {
eventQueue.push(event);
if (eventQueue.length > 0) {
if (lastFlushTime + 2000 > Date.now()) {
flushEventQueue(); // flush immediately if recent
} else {
setTimeout(flushEventQueue, 2000); // debounce
}
}
}
// Enrich an incoming gaEvent message from the content script with tracking IDs.
async function sendGaEvent(event) {
const clientId = await getOrCreateClientId();
const sessionId = await getOrCreateSessionId();
const params = event?.params ?? {};
const enrichedEvent = {
...event,
params: {
...params,
client_id: clientId,
session_id: sessionId,
engagement_time_msec: ENGAGEMENT_TIME_MS,
},
};
queueEvent(enrichedEvent);
}
// Message listener: handles gaEvent messages relayed by the content script
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
// ... other message types ...
if (message.gaEvent) await sendGaEvent(message.gaEvent);
// ...
sendResponse({ message: 'Message received' });
});- www.google-analytics.com
Google Analytics Measurement Protocol (GA4) endpoint. Receives page_view events with ChatGPT URLs, titles, and a persistent install UUID on every navigation. Operated by Google.
What it can do
Permissions this extension asks for, as declared in version 4.3.1. 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 4.4.2, which we have not unpacked yet.
Store data in your browser
storage
Where it sends data
Destinations our analysis observed Voice Control for ChatGPT 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.google-analytics.comwidely used
Voice Control for ChatGPT sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.