A Chrome extension (Manifest V3) that injects two native-looking buttons into the major web AI assistants — ChatGPT, Gemini and Claude:
- Anonymize – analyzes the text typed in the composer using the
bardsai/eu-pii-anonimization-multilangmodel (running locally in the browser) and replaces detected sensitive values with deterministic tokens, e.g.Jan Kowalski→[PERSON_NAME_1],Klinika MSZ Hospital→[ORGANIZATION_NAME_1],Warsaw→[LOCATION_1]. After the replacement you can submit the prompt with Enter as usual. - De-anonymize response – on a generated assistant response it maps
every token (e.g.
[PERSON_NAME_1], including occurrences without brackets that appear inside JSON keys) back to the original value and highlights the replaced spans. Copying that response from the host's own Copy button is also intercepted so the clipboard gets the de-anonymized text — but only if you actually clicked De-anonymize on that message; otherwise tokenized text is copied as-is.
Supported hosts (declared in manifest.json):
| Host | Composer editor | Notes |
|---|---|---|
chatgpt.com |
Lexical / ProseMirror | Anonymize lives in the footer pill row (logged-in) or next to the "+" (guest). |
chat.openai.com |
Lexical / ProseMirror | Same as above. |
gemini.google.com |
Quill (<rich-textarea>) |
Anonymize sits right after the "+" / Add files button. |
claude.ai |
Tiptap (ProseMirror) | Anonymize sits right after the "+" / Add files button. |
The token mapping is kept in the tab's memory (it is never sent anywhere). Inference runs inside a Web Worker hosted by an offscreen document — the model (~300 MB, q8) is downloaded from HuggingFace Hub on first use and cached by the browser.
The detection and post-processing logic is identical to the hosted demo in
this repo (public/worker.js, public/worker-lib.js).
-
Install the project's dependencies (if you haven't yet):
npm install
If Transformers.js / onnxruntime-web are not in the root
node_modules, install them inside the extension folder:cd extension npm install cd ..
-
Copy the runtime files into
extension/vendor/:cd extension npm run buildThe script copies the minified
transformers.min.jsand the required*.wasm/*.mjsfiles fromonnxruntime-webso the extension can load them from its ownchrome-extension://…origin (MV3 CSP blocks script loads from CDNs). It also generatesicons/icon{16,32,48,128}.pngfromicons/icon-source.svg. -
In Chrome open
chrome://extensions, enable Developer mode and click Load unpacked, pointing at theextension/folder.
-
Click the extension icon → Download model (~300 MB). You can watch the progress in the popup. When the model is ready the status dot turns green.
-
Open one of the supported hosts: chatgpt.com, gemini.google.com or claude.ai. The Anonymize shield button appears next to the composer's "+" button.
-
Type a prompt containing personal data, e.g.:
Jan Kowalski is 23 years old and was admitted to Klinika MSZ Hospital in Lublin. He is currently under observation. Generate a report based on this.
-
Click Anonymize. The editor text will be replaced with the tokenized version:
[PERSON_NAME_1]is 23 years old and was admitted to[ORGANIZATION_NAME_1]in[LOCATION_1]. … -
Press Enter to send the safe prompt to the assistant.
-
Once a response arrives, click the De-anonymize shield icon on the action bar below the assistant message. The tokens in the response are replaced with their original values and highlighted in green (the
data-tokenattribute stays in the DOM for reference).
The popup also shows the current token mapping and a Clear button (useful when starting a new topic). The mapping is also wiped when the tab is reloaded.
extension/
manifest.json MV3 manifest, permissions + host_permissions for huggingface.co
background.js service worker — manages the offscreen document,
forwards messages and broadcasts model status
offscreen.html/.js DOM context that hosts the model Worker
worker.js the same classifier as public/worker.js
worker-lib.js shared post-processing (identical to public/worker-lib.js)
content.js per-host adapters (ChatGPT / Gemini / Claude):
composer detection, button injection, anonymize /
de-anonymize logic, copy interception
content.css styling for the injected buttons and toast
popup.html/.js popup with "Download model" button and token mapping
build.mjs copies vendor runtime (transformers + ort)
build-icons.mjs generates PNG icons from icons/icon-source.svg
pack.mjs packs the extension into dist/*.zip for Chrome Web Store
vendor/ (gitignored) Transformers.js + ORT WASM runtime files
Flow:
content.js ── classify ─► background.js ── ensureOffscreen + sendMessage ─► offscreen.js ── postMessage ─► worker.js
│
onnx / transformers.js
▼
HuggingFace Hub (GET)
All model fetches go from the offscreen document to HuggingFace Hub
(declared in host_permissions). No prompt or response is ever sent
outside the extension.
- Tokens use the
[ENTITY_TYPE_N]format (with brackets). This keeps the de-anonymization regex unambiguous and the assistants treat them as placeholders. Bare occurrences that the model sometimes emits inside structured output (e.g. JSON keys like"PERSON_NAME_1") are also matched on word boundaries. - The same original value is always mapped to the same token within a tab session (dedupe by normalized value).
- Each host's composer selector is matched on multiple stable hooks. If a host changes its markup the extension shows a toast saying "Text input field not found".
- The model requires ~300 MB of memory.