Is ChatGPT Assistant - GPT Search safe?
ChatGPT Assistant - GPT Search sends everything you type into Google, Bing and other search engines to a third-party server, not to ChatGPT.
When you search on Google, Bing, DuckDuckGo, Yahoo, Baidu, Kagi, Yandex, Naver, Brave or Searx, the extension reads your typed query and sends it to search-chat.ai along with a hardcoded API key, then displays that response in a sidebar styled as a ChatGPT answer. The genuine call to OpenAI's ChatGPT backend is present in the code but never used; a 'Continue Chat Thread' link points to a fixed, fake conversation ID rather than a real OpenAI session.
Who publishes itMaster Tools - 1 other listing from the same operator, 1 of them carrying a finding
Master Tools - 1 other listing 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.
Same store account
1 other listing published from this account, 100k+ users between them. 1 of them carries a finding.
Shared hosts - 1 hostname
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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
ChatGPT search extension proxies queries through an undisclosed backend
Code analysis shows the extension reads your typed query on Google, Bing and eight other search engines, sends it to search-chat.ai with a hardcoded key, then shows that reply as a ChatGPT answer with a constant, non-real conversation ID.
You type a search on Google, Bing, DuckDuckGo, Yahoo, Baidu, Kagi, Yandex, Naver, Brave, or a Searx instance and the extension's inline answer panel activates.
content.js reads your typed query and bg.js sends it to search-chat.ai with a hardcoded key, then shows that reply as the ChatGPT answer.
The real request to chatgpt.com/backend-api/conversation is present in the code but is commented out and never runs.
| Field | Value | Why it matters | |
|---|---|---|---|
Your search query | best noise cancelling headphones under $200 | The exact text you typed into the search box is sent to search-chat.ai as plain text. | |
Vendor API key | key=VuCP8hvfZNh23ksGphx3CuLN | A fixed key baked into the extension authenticates every request; the same key ships in every install. | |
Conversation ID shown to you | 122hhbd-sjdhsgd | The reply always carries the same constant ID, not a real ChatGPT conversation, so the linked thread never opens an actual OpenAI session. |
The commented-out ChatGPT call next to the live search-chat.ai proxy
function runSearchCapture() {
// engineConfig (was Mu) is looked up earlier by matching location.hostname
// against a table of ~10 search engines (google, bing, yahoo, duckduckgo,
// baidu, kagi, yandex, naver, brave, searx).
if (!engineConfig) return false;
const searchInput = querySelectorFromList(engineConfig.inputQuery);
searchInput && searchInput.value && (async (typedQuery, engineConfig) => {
// Builds and mounts the sidebar DOM node into the search results page.
const sidebarWrapper = document.createElement("section");
const sidebarRoot = document.createElement("div");
sidebarWrapper.appendChild(sidebarRoot);
sidebarWrapper.className = "chat-gpt-container";
const settings = await getUserSettings();
sidebarWrapper.classList.add(
(settings.theme === "auto" ? systemPrefersDark() : settings.theme) === "dark"
? "gpt-dark"
: "gpt-light"
);
const sidebarContainer = querySelectorFromList(engineConfig.sidebarContainerQuery);
if (sidebarContainer) {
sidebarContainer.getElementsByClassName(sidebarWrapper.className)[0]?.remove();
sidebarContainer.prepend(sidebarWrapper);
} else {
sidebarWrapper.classList.add("sidebar-free");
const appendTarget = querySelectorFromList(engineConfig.appendContainerQuery);
if (appendTarget) {
appendTarget.getElementsByClassName(sidebarWrapper.className)[0]?.remove();
appendTarget.appendChild(sidebarWrapper);
}
}
// Mounts the sidebar React component with the TYPED SEARCH QUERY as its
// `question` prop. This is the value that ends up at search-chat.ai.
mountComponent(SidebarApp, {
question: typedQuery,
triggerMode: settings.triggerMode || "always"
}, sidebarWrapper);
})(searchInput.value, engineConfig);
}async function fetchOpenAISessionToken() {
// Fetches a REAL OpenAI access token from the user's own chat.openai.com
// session cookie. This token is fetched but then never actually used to
// call any OpenAI endpoint for the answer itself (see below).
if (cachedToken.get(TOKEN_KEY)) return cachedToken.get(TOKEN_KEY);
const sessionResp = await fetch("https://chat.openai.com/api/auth/session");
if (sessionResp.status === 403) throw new Error("CLOUDFLARE");
const sessionJson = await sessionResp.json().catch(() => ({}));
if (!sessionJson.accessToken) throw new Error("UNAUTHORIZED");
cachedToken.set(TOKEN_KEY, sessionJson.accessToken);
return sessionJson.accessToken;
}
async function handleSidebarQuery(port, request) {
// request.question is the raw text the user typed into the search box.
const typedQuery = request.question;
let openaiToken;
try {
openaiToken = await fetchOpenAISessionToken();
} catch (err) {
port.postMessage({ error: err.message });
openaiToken = "";
}
// ... builds a full, realistic ChatGPT-shaped request body (model, message
// history, timezone, client_contextual_info, etc) that is never sent ...
// DEAD CODE: this is the real ChatGPT call. It is commented out in the
// shipped extension and never executes.
//
// const requirementsResp = await fetch(
// "https://chatgpt.com/backend-api/sentinel/chat-requirements",
// { method: "POST", headers: { Authorization: `Bearer ${openaiToken}`, ... } }
// );
// const requirementsToken = (await requirementsResp.json()).token;
// await streamFetch("https://chatgpt.com/backend-api/conversation", {
// method: "POST",
// headers: { Authorization: `Bearer ${openaiToken}`, ... },
// body: JSON.stringify(chatGptRequestBody),
// onMessage(streamChunk) { /* forwards real ChatGPT tokens to the sidebar */ }
// });
// LIVE CODE: instead of the block above, every query is proxied through a
// third-party endpoint with a key hardcoded into every install.
const response = await fetch(
"https://search-chat.ai/api/search.php?" +
new URLSearchParams({
q: typedQuery,
key: "VuCP8hvfZNh23ksGphx3CuLN"
})
);
const result = await response.json();
// The sidebar is told this came from a ChatGPT conversation, but the id is
// a hardcoded constant, not a real OpenAI conversation id.
port.postMessage({
text: result.text,
messageId: 12,
conversationId: "122hhbd-sjdhsgd"
});
port.postMessage({ event: "DONE" });
}Sends a search query to the same search-chat.ai endpoint and hardcoded key the extension uses, so you can see the raw third-party reply independent of any real ChatGPT session.
#!/usr/bin/env node
// search-chat-proxy-check.js
//
// Sends a search query to the same endpoint and hardcoded key that
// gjfkjdaeefjopkbbdbbbmjkmopjpkakn's background service worker uses in
// place of a real ChatGPT call, so you can see the raw third-party
// response independent of any OpenAI account or session.
//
// Usage: node search-chat-proxy-check.js "your test query"
const HARDCODED_KEY = "VuCP8hvfZNh23ksGphx3CuLN"; // read from the extension's bg.js
const query = process.argv[2] || "test query for verification";
const url =
"https://search-chat.ai/api/search.php?" +
new URLSearchParams({ q: query, key: HARDCODED_KEY }).toString();
(async () => {
console.log("Requesting:", url);
const res = await fetch(url);
console.log("HTTP status:", res.status);
const body = await res.json().catch(() => null);
console.log("Response body:", JSON.stringify(body, null, 2));
console.log(
"\nThe extension shows this response as a ChatGPT answer. It never " +
"sends this query to https://chatgpt.com/backend-api/conversation; " +
"that call exists in bg.js only as commented-out, unused code."
);
})();
- 1node search-chat-proxy-check.js "your test query"
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
What it can do
Permissions this extension asks for, as declared in version 1.0.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on every site you visit
<all_urls>
Store data in your browser
storage
Add items to the right-click menu
contextMenus
Block and redirect the requests your browser makes
declarativeNetRequest
Act on the current tab, but only after you click the extension
activeTab
Where it sends data
Destinations our analysis observed ChatGPT Assistant - GPT Search 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.
- search-chat.ai
ChatGPT Assistant - GPT Search sends data to search-chat.ai. One other extension we have analysed sends data here.