The plus button handler in the pre-send PII warning had two bugs:
1. It read/wrote mappings directly via the storage bridge (api.storage.local),
bypassing the Storage module's encryption layer. When at-rest encryption was
enabled, getStorageData returned an encrypted blob instead of an array,
causing .push() to throw a TypeError that silently aborted the handler —
the mapping was never saved and replaceInInput never ran.
2. Unlike the ignore button which immediately removes the DOM item, the plus
button relied on a re-scan at 150ms to dismiss the notification. If
replaceInInput didn't stick (e.g. React-controlled inputs), the re-scan
found PII again and the notification persisted.
Fix: Route mapping creation through the background service worker via a new
'add:mapping' message handler (which uses Storage.addMapping with proper
encryption support), and immediately dismiss the notification item from the
DOM like the ignore button does.
https://claude.ai/code/session_01RfzvB5sHah326acr8Xa7Jn
Root cause: when the popup opens for the first time it calls
addProfile('Personal') + updateProfile(...) to create a default empty
profile. Both call saveProfiles, which was unconditionally setting
ss_lastModified: Date.now(). This made the new browser's local
timestamp look like right now — newer than any Gist data pushed by
the source browser — so every pull returned "Already up to date"
without ever prompting for a password or importing anything.
Fixes:
1. storage.js saveProfiles: only advance ss_lastModified when at
least one profile contains real PII (non-empty real value in
names, emails, usernames, phones, or catchAllEmail). Creating the
default empty profile structure on first install leaves
ss_lastModified at 0 so pulls correctly see remote data as newer.
2. sync.js pushToGist: persist ss_last_push_time and
ss_last_push_source alongside ss_gist_id so the source browser
(which only pushes) can also show its last activity time.
3. options.js: display both "Pushed: <time>" and "Pulled: <time>"
in the Gist status area on page load, giving both browsers
meaningful feedback.
4. service-worker.js: after a successful auto-sync pull, broadcast
vault:unlocked to all open content-script tabs so substitution
works immediately without a page reload.
https://claude.ai/code/session_01QJnEnLfbXKR5FSCQ3Qfs53
When at-rest encryption is enabled, injector.js detects encrypted blobs
in storage, passes empty config to the content script, and waits for a
vault:unlocked broadcast from the background. That broadcast was only
ever triggered when the user explicitly entered their password in the
popup. After a Gist/URL sync import (which writes newly-imported data
in encrypted form), no page ever received the decrypted config, so
substitution silently stopped working.
Two fixes:
1. injector.js: when isLocked is true (encrypted blobs detected), send
vault:request-unlock to the background. If the key is already cached
(e.g. the user authenticated during the sync pull), the background
responds immediately with vault:unlocked containing the decrypted
data. This fixes every new page load after a sync import.
2. service-worker.js: add vault:request-unlock handler that checks
Storage.isLocked() and, if the key is available, reads decrypted
mappings/identity/settings and sends vault:unlocked back to the
requesting tab.
3. options.js: after a successful Gist or URL pull, send vault:unlocked
to the background so it broadcasts decrypted data to all currently-
open tabs immediately, without requiring a page reload.
https://claude.ai/code/session_01QJnEnLfbXKR5FSCQ3Qfs53
Going back to a known-good baseline. This version had:
- Working reveal mode with CSS Highlight API
- Working substitution (fetch + XHR hooks)
- Smart patterns (names, emails, phones, usernames)
- Encryption/sync (password, TOTP, WebAuthn)
- Multiple identity profiles
- Activity log
- Secret scanner
- Auto-detect PII warnings
- Pre-send PII detection
Kept current manifests (UUID, data_collection_permissions, version).
No renames applied — uses original naming (secretScanning, PPI, etc).
Will re-apply renames and new features from this working base.
https://claude.ai/code/session_01KF4i7Ra7zCEDskxDBaNtcT
Restored injector.js from v2.0.14 (commit 9a11896) which:
- Requests decrypted config from background via get:decrypted-config
message instead of passing empty arrays when data is encrypted
- Handles the identity.profiles merge correctly for background responses
- Passes ss_settings directly (not checking _ssLocalEncrypted which
caused settings loss)
Added missing get:decrypted-config message handler to service-worker.js
which returns decrypted mappings, identity, and settings via Storage
module.
https://claude.ai/code/session_01KF4i7Ra7zCEDskxDBaNtcT
Added copilot.microsoft.com to host_permissions, content_scripts
matches, and BUILTIN_URL_PATTERNS in both Chrome and Firefox manifests
plus the service worker.
Updated README:
- Added Copilot to supported services table
- Added Edge and Brave to browser list
- Added note about desktop apps (can't intercept, use web version)
- Removed old duplicate browser note
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
New AI sites: Perplexity, Copilot, DeepSeek, HuggingChat, Poe
Developer/support: GitHub, GitLab, Reddit (www + old), Stack Overflow, Pastebin
The existing interception is service-agnostic — it scans all JSON strings
in POST/PUT/PATCH requests through the 4-stage substitution pipeline.
No site-specific handling needed; all sites use the same method.
https://claude.ai/code/session_01KF4i7Ra7zCEDskxDBaNtcT
When at-rest encryption is enabled, storage.local contains encrypted
blobs. The injector reads raw storage (content script world, no
access to IndexedDB CryptoKey) and sees { _ssLocalEncrypted: true }.
It passed empty config to content.js → no mappings → no substitution.
This is why Firefox stopped working after encryption was enabled.
Chrome/Brave worked because the user hadn't set up encryption there.
Fixed: injector now detects encrypted data and asks the background
script for decrypted config via 'get:decrypted-config' message.
The background uses the Storage module (which has IndexedDB access)
to decrypt and return the data. Falls back to empty config if the
vault is actually locked.
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
Replace tab-load-only injection with scripting.registerContentScripts()
so custom domains inject at document_start (like built-in sites) and
persist across service worker restarts. Scripts re-register automatically
when domains change. Removed domains now revoke browser permissions.
https://claude.ai/code/session_01KF4i7Ra7zCEDskxDBaNtcT
All sensitive data (identity, mappings, activity log) is now AES-256
encrypted in browser.storage.local when sync encryption is enabled.
TOTP secret is also encrypted at rest using the derived key.
Vault unlock flow:
- On browser restart, extension detects locked state (encrypted data,
no cached CryptoKey) and shows LOCK badge in red
- Popup shows a full-screen unlock prompt with password field,
optional TOTP, and biometric button
- After unlock, background decrypts and broadcasts data to all tabs
- Content scripts start with empty config when locked; receive
decrypted config via vault:unlocked message after unlock
- Injector skips encrypted blobs in storage change events
Storage module changes:
- _readSecure / _writeSecure transparently encrypt/decrypt
- encryptExistingData() migrates plaintext → encrypted on setup
- decryptAllData() restores plaintext when encryption is disabled
- isLocked() checks for encrypted data + missing key
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
Notification system:
- When sync applies data (file folder, browser account, or sync code),
ss_sync_notification is written to local storage with the source.
- Service worker catches it via storage.onChanged, shows a purple 'SYN'
badge on the extension icon that persists until Options is opened, and
fires a desktop notification ('Settings updated via sync folder — open
Options to review').
- Clicking the desktop notification opens the Options page directly.
- On service worker wake, SYN badge is restored if the notification was
not yet dismissed.
- Opening Options clears ss_sync_notification, resets the badge, and
sends a sync:notification-seen message to the service worker.
- Added 'notifications' permission to both manifests.
Cloud storage clarity:
- Options page now explicitly lists that the folder sync works with any
cloud storage that has a desktop sync client: Dropbox, OneDrive, Google
Drive, iCloud Drive, Box, pCloud, Nextcloud, Synology Drive, etc.
https://claude.ai/code/session_01TKpSR9M8JgHLXCp5CeDsQP
Bug fixes:
- Date (possible DOB) pattern now requires context words (born, birthday,
dob, etc.) before firing — prevents spurious warnings on page-load API
calls that happen to contain ISO dates in conversation history.
- Highlight regex now uses word boundaries (\b) so short substitute values
(e.g. "aud") no longer match inside unrelated words like "Claude".
- Both TreeWalkers in content.js now skip the extension's own UI elements
(.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge) to
prevent the highlight API from marking text in the extension's banners.
Settings sync:
- New src/lib/sync.js: exportSyncCode / importSyncCode (base64 JSON) for
manual copy-paste across any browser combination. Newest lastModified
timestamp wins; force flag available to override.
- browser.storage.sync support: when "Browser account sync" is enabled the
extension automatically pushes/pulls via Firefox Sync or Chrome account,
chunked to stay within per-item quota limits.
- storage.js now writes ss_lastModified on every save so conflict resolution
has an accurate timestamp.
- service-worker.js listens for both local and sync storage changes to keep
all copies in sync.
- New "Sync Between Browsers" section in options.html with Generate/Copy/
Import Sync Code UI and the browser sync toggle.
https://claude.ai/code/session_01TKpSR9M8JgHLXCp5CeDsQP
Interception is now completely inactive until the user configures
at least one identity field or explicit mapping. Before that:
- Icon shows gray (unconfigured)
- No fetch/XHR hooks fire
- First-run banner tells user to set up
Custom domains: clicking "Add Domain" in Options now triggers
the browser's native permission prompt via permissions.request().
No more manual chrome://extensions site access step.
Icon states are now:
- Gray = unconfigured (nothing will happen)
- Black = active and protecting
- Blue = reveal mode on
- Red = manually disabled
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Activity log fix:
- Injector now writes directly to storage.local in addition to
sending runtime messages to the background worker. This fixes
the issue where MV3 service worker sleep caused messages to be
silently dropped.
Dynamic icon colors:
- Black "SS" = active, normal
- Blue "SS" = reveal mode on
- Red "SS" = Silent Send disabled
- Icons generated via OffscreenCanvas in the service worker
- Updates on every settings change and keyboard shortcut toggle
Also adds keyboard shortcuts section to Options page showing
current bindings and how to customize them per browser.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
- Add custom domain support in Options page so users can add
self-hosted AI services (e.g. https://ai.myserver.com)
- Background worker dynamically injects content scripts on
custom domains using scripting.executeScript
- Add optional_host_permissions so Chrome can grant per-domain access
- Rewrite README: add clone step to Firefox instructions, clarify
what "credentials" means in step 3, add Windows commands alongside
Mac/Linux for every terminal step
- Bump version to 0.2.0
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Extend Silent Send to intercept API requests on all major AI chat
services. Each service has different API shapes:
- ChatGPT: /backend-api/conversation with content.parts arrays
- Grok: GraphQL + /2/grok/add_response with message field
- Gemini: form-encoded f.req with nested arrays (+ generateContent)
- OpenWebUI: /api/chat and /ollama/api/chat (self-hosted)
All services share the same substitution pipeline. Manifests updated
for both Chrome and Firefox with host_permissions for all domains.
OpenWebUI supported via localhost/127.0.0.1 for self-hosted instances.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Add manifest.firefox.json for Firefox MV3 (gecko ID, background
scripts instead of service_worker, options_ui). Introduce
browser-polyfill.js shim so all modules use whichever API is
available (browser.* or chrome.*). Add build.sh to target
chrome, firefox, or both.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Chrome Manifest V3 extension that intercepts personal data and
substitutes it with user-defined replacements before sending to
Claude.ai. Hooks fetch() in the page's main world to catch API
requests, with bidirectional substitution (real→fake on send,
fake→real on display via reveal mode).
Includes popup UI with mapping management, live test/diff view,
activity log with badge count, options page with import/export,
and Shadow DOM traversal for Claude.ai compatibility.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw