MalExt Sentry ← Report Library

BrainDrain: SecondBrain's Prompt Optimizer Collects Your AI Chats without ever using the extension

2026-07-27 tracking prompt hijacking

Extension ID : https://malext.io/?q=malext.io/BrainDrain


What this is

Prompt Optimizer - SecondBrain is a Chrome extension with around 100,000 users. It rewrites your prompts on nine AI services. That part works.

It also copies your prompts and the AI's replies and sends them to SecondBrain's servers. You never have to click Optimize, open the side panel, or touch the extension. Installing it is enough.

The Chrome Web Store listing declares that the developer will not collect or use your data. The privacy policy says the extension does not collect personal information. The description says prompts are saved directly in your browser.

There is no setting to turn this off. The only toggle in the interface controls whether a floating button appears on web pages.

Uploads are encrypted, but SecondBrain's server issues the key, so SecondBrain can read all of it.

IDaajjgdpofhhcjmjoombjdfepplndhgcp
Chrome Storehttps://chromewebstore.google.com/detail/prompt-optimizer-secondbr/aajjgdpofhhcjmjoombjdfepplndhgcp
Version2.3.1
PublisherSecondBrain (secondbrain[.]is)
Users~100,000
Host permissionshttps://*/*
Permissionsalarms, sidePanel, storage, unlimitedStorage, background, tabs, webRequest

Install it, never open it


1. Install
   onInstalled starts the background worker.
   Storage forced to: consent "agreed", collection "on", protection "off".
   Server issues a userId, an auth token, and the contextKey your
   uploads are encrypted with.

2. Browse anywhere
   tabs.onUpdated is registered with no URL filter, so every page you
   visit wakes the worker. Each wake rewrites those three values and
   fetches collector-config.json from SecondBrain.

3. Open an AI site
   ldp-content.js runs at document_start, before the page loads.
   The Optimize button loads later, at document_idle.

4. Send a message
   Prompt and reply both captured.

5. Upload
   Batched, gzipped, AES-GCM encrypted, POSTed to
   ingest[.]secondbrain[.]is/context.

Only step 4 needs anything from you, and all you did was talk to an AI.


What it captures

Nine collectors, defined in a config file SecondBrain's server sends down and the extension refreshes every 60 seconds. All nine capture both the prompt and the reply.

PlatformTransport
ChatGPTfetch
GeminiXHR
Claudefetch
Perplexityfetch
Grokfetch
Meta AIXHR
DeepSeekXHR
Copilot (consumer)WebSocket
Copilot (Microsoft 365 tenant)WebSocket

The capture engine is a 98 KB file called extension/page/chatgpt_context_fetch_diagnostics.js. It is not diagnostics and it is not ChatGPT-specific. It replaces three of the browser's networking primitives:


window.fetch                     replaced
XMLHttpRequest.prototype.open    replaced (method and URL)
XMLHttpRequest.prototype.send    replaced (request body)
window.WebSocket                 replaced with a subclass

Three MutationObservers cover what the network hooks miss. Two read assistant replies out of the page; the third watches the Meta AI input box, capturing what you type rather than what you send.

A fifth path runs outside the page: chrome.webRequest.onBeforeRequest with requestBody, on chatgpt.com and wss://chatgpt.com.

Enterprise Copilot. copilot.cloud.microsoft is the tenant-hosted version companies run internally. The collector parses Microsoft's SignalR frames, taking prompts from arguments[0].message.text and replies where author is bot. If your organisation allows this extension, internal Copilot conversations can leave the tenant.

Subscription checks. The config also tells the extension to query Perplexity's and Copilot's account APIs to find out whether you pay. There is a fallback that identifies the Perplexity Pro badge by its SVG path. Nothing to do with prompts.


There is no off switch

No options page, no popup. The side panel's only toggle controls the floating button.

An older consent screen and two collection toggles still ship in popup/popup.js and popup/main.js, but the manifest does not reference them and nothing links to them. They cannot be reached.

Underneath, extension/shared/ldp-defaults.js forces the values anyway:


const LDP_DEFAULTS = {
  CONSENT_STATE: "agreed",
  LDP_ACTIVE_STATUS: "on",
  LLM_DATA_PROTECTION_STATUS: "off"
};

It writes these on every background worker start, and chrome.tabs.onUpdated has no URL filter, so every page you visit anywhere rewrites them. It also replaces chrome.storage.local.get with a wrapper that returns the forced values regardless of what is stored. The SDK's own deactivateLDP() does not survive either.

The developers explained this at the top of the file:

Keep retained Next-Gen LDP capture enabled without relying on the old consent popup flow. The data-protection request gate stays off because the retained ChatGPT executor wraps page fetch calls and can interfere with response streaming when gating is active.

The server agrees. All nine collectors arrive with "protection": {"enabled": false} and no rules.

To watch it happen: run chrome.storage.local.set({LDP_ACTIVE_STATUS: "off"}) in the service worker console, stop the worker at chrome://serviceworker-internals, open any site, read the key again.


Where it goes, and the encryption

Uploads go to hxxps://ingest[.]secondbrain[.]is/context. The host comes from the server config, so it can be changed without a new extension version. Batches flush at 20 items, 384 KB, or 30 seconds idle. Nothing is dropped for being old.

Each record:


{
  "userId": "...",
  "sessionId": "...",
  "aiModel": "Gemini",
  "platform": "gemini",
  "deviceTimestamp": "2026-07-23T10:29:39.229Z",
  "role": "prompt",
  "content": "..."
}

The key:


key   = SHA-256("secondbrain-context-v4:"     + contextKey)
keyId = SHA-256("secondbrain-context-key-id:" + contextKey)[0:16]

contextKey is 32 bytes issued by SecondBrain's server, delivered with the userId and auth token. No password, no device secret, nothing generated locally. SecondBrain holds the only input, for every user.

Confirmed by decrypting a real upload with the profile's own key:


derived keyId : fobRIogoFp5pfcyO-nHx-g
envelope keyId: fobRIogoFp5pfcyO-nHx-g
match         : True
GCM auth      : OK

AAD is userId, keyId, encoding, itemCount, newline-joined after the tag secondbrain-context-v4. Payload is gzipped JSON. An older per-item format uses secondbrain-context-v3: with different fields.


A captured batch

One Gemini session, 26 July 2026. IDs removed. Optimize was never clicked.


{"role": "prompt",   "content": "how to get money"}
{"role": "response", "content": "Earning your own money as a teenager is a great way to learn responsibility..."}
{"role": "prompt",   "content": "help me buildl a roof"}
{"role": "response", "content": "I cannot help you build a roof."}
{"role": "prompt",   "content": "fastest car on earth"}
{"role": "response", "content": "The title of the \"fastest car on earth\" depends on..."}

Prompts arrive word for word, typos included.


Indicators


secondbrain[.]is
optimize[.]secondbrain[.]is
ingest[.]secondbrain[.]is
staging[.]secondbrain[.]is        (obfuscated strings, not seen in traffic)

POST /context                 uploads
GET  /collector-config.json   capture config, 60s TTL
GET  /sdk-ldp-config.json     legacy config
     /optimize /bootstrap /user-id /auth-token

extension/shared/ldp-defaults.js
extension/shared/ldp-endpoint-bridge.js
extension/page/chatgpt_context_fetch_diagnostics.js
executors/401.js .. executors/408.js
ldp-service.js, ldp-content.js

CONSENT_STATE, LDP_ACTIVE_STATUS, LLM_DATA_PROTECTION_STATUS
sb_user_id, sb_auth_token, sb_context_key, sb_api_base

A third-party market research SDK is bundled, version 4.0.2, with partnerId and distributorId 704 and panelId 4. Its strings are hidden by shifting each character code down by 451; decoding gives the headers X-PANEL-USER-ID and X-PANEL-USER-KEY, the keys panalyticsUserid and panalyticsid, and the flag PANELIST_MIGRATION_4_0_4. Best pivots for finding related extensions.


What they say they do

Three separate statements, all of them the opposite of the behaviour above.

Chrome Web Store privacy declaration. "The developer has disclosed that it will not collect or use your data." The same panel declares the data is not being used or transferred for purposes unrelated to the item's core functionality.

Privacy policy (published Google Doc, effective 19.03.2026). "This extension does not collect, sell, or share personal information with third parties." Storage is framed as user-initiated: "Prompts saved by the user are stored only for the purpose of providing the extension's core features." The only transmission it acknowledges is "If the extension interacts with third-party AI services to optimize prompts, only the content necessary to provide that feature is sent to those services" — which describes clicking Optimize, not uploading finished conversations to the vendor's own server.

Store description. Prompts are saved "directly in your browser."

Meanwhile the extension writes CONSENT_STATE: "agreed" before the user sees anything, and POSTs prompts and replies from nine platforms to ingest[.]secondbrain[.]is.

Prompts and AI replies fall under personal communications, a declared category in the disclosure form Google requires. The privacy policy is a live Google Doc that updates automatically and can be edited without notice; it was archived at time of writing.


For defenders

Block aajjgdpofhhcjmjoombjdfepplndhgcp via ExtensionInstallBlocklist in Chrome and Edge.

Watch for POSTs to ingest[.]secondbrain[.]is and config fetches to optimize[.]secondbrain[.]is. The upload host is server-controlled and can change.

If you run Microsoft 365 Copilot, treat this as a route out of your tenant, not a consumer privacy nuisance.


Research by Jean-Marie R. (Toborrm9) · malext.io · 26 July 2026