The early hook approach (inline script, then external early-hook.js)
kept breaking Firefox due to CSP restrictions and timing issues with
the async external script load. Each fix for ChatGPT introduced a
new regression for Firefox/Brave.
Reverted to the original simple approach:
- Injector reads storage, injects content.js via <script src="...">
- content.js captures window.fetch at load time and patches it
- No inline scripts, no early hooks, no __ssOriginalFetch globals
This is what worked on Claude.ai across all browsers before the
ChatGPT fix attempts. ChatGPT support may need a different approach
later (possibly using declarativeNetRequest for header-only changes,
or a ChatGPT-specific content script), but it should not break the
core functionality on Claude.ai.
Kept the Request object handling in the fetch interceptor (needed for
some frameworks) but removed all early hook dependencies.
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
The inline <script> with textContent was blocked by claude.ai's
Content Security Policy on Firefox (Chrome is more permissive).
No fetch interception = no substitution = completely broken on FF.
Fixed by moving the early fetch hook to its own file (early-hook.js)
loaded via <script src="..."> which is CSP-compliant. Added to
web_accessible_resources in both Chrome and Firefox manifests.
Also fixed duplicate 'const api' declaration in injector.js that
would have crashed the content script.
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
Bug 1 - Reveal mode not working (all browsers):
sessionSubstitutions stored "ademo demo" (full name) as key, but
buildRevealPairs looked up "ademo" and "demo" individually. No match,
no reveal pairs, reveal did nothing. Fixed by also storing individual
words from multi-word replacements so both "ademo demo" AND "ademo"
AND "demo" are in the map.
Bug 2 - Missing await on _handleDecryptedMeta (sync.js):
Two call sites returned the Promise instead of the resolved value.
Downstream code checking decResult.data got undefined. Added await.
Bug 3 - Profile selector broken by safeHTML (popup.js):
DOMParser.parseFromString wraps content in <html><body> which
mangles <option> elements when moved to a <select>. Replaced with
new Option() DOM API which creates proper option elements.
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
Root cause: when importing an encrypted sync code from another device,
_decryptFromSync was replacing the local encryption config (salt) with
the source device's salt. This caused:
1. Local key cache derived from wrong salt
2. Local data (encrypted with local salt) became unreadable
3. _applyData tried to write with the wrong key
Fixed with a complete refactor of cross-device decryption:
- authenticateForSync() derives a TEMPORARY key using the source salt
- Temporary key stored separately as 'tempSyncKey' in IndexedDB
- Local encryption config and cached key are NEVER modified
- After decryption, _applyData writes via _writeSecure using the
LOCAL key (which uses the local salt)
- _handleDecryptedMeta extracted for code reuse
Also:
- Options.js auth handler detects pending sync import and routes to
authenticateForSync instead of regular authenticate
- After auth success, automatically retries the import
- README updated: imported passwords are protected (dots in UI,
vault password to reveal, AES-256 encrypted at rest)
- Bumped to v2.0.9
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
ChatGPT (and potentially other AI services) calls fetch() with a
Request object as the first argument: fetch(new Request(url, opts))
instead of fetch(url, opts). The interceptor only handled the second
form, so ChatGPT's conversation requests passed through unmodified.
Fixed by handling both fetch signatures:
- fetch(url, options) — existing path
- fetch(Request) — new: extracts URL, method, headers, reads body
via request.text() for JSON/text content types
Also handles non-string body types:
- Blob → text via blob.text()
- ArrayBuffer → text via TextDecoder
- URLSearchParams → string via toString()
These cover the various ways modern frameworks call fetch().
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
Set Chrome, Firefox, and package.json all to version 2.0.0
(major bump reflecting encrypted storage, document scanning,
org/team features, sync improvements).
Sign script rewritten:
- Tries current version first instead of always bumping
- Only bumps on "version already exists" errors
- Handles rate limiting by parsing throttle duration from error
and waiting the exact time (not blindly retrying)
- Only updates source files on successful signing (not before)
- Reduced max attempts to 5 (with proper backoff, shouldn't need more)
- Commits version bump only after successful sign
https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
License: Changed from MIT to BSL 1.1. Free for personal use,
commercial use requires a paid license. Auto-converts to MIT
on March 26, 2030.
Export/Import: Options page now has "Transfer Data" section:
- Export All (plain) — JSON file with all identities, mappings, settings
- Export Encrypted — AES-256-GCM with PBKDF2 password derivation,
saved as .ssbackup file
- Import — handles both plain and encrypted backups, prompts for
password if encrypted
Crypto uses Web Crypto API (browser-native, no dependencies):
100k PBKDF2 iterations, random salt + IV per export.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Add LICENSE file (MIT). Explain in README that reveal mode is a
local display change only — the AI never received the real data.
Reveal exists so users can copy paths/commands from AI responses
and paste them directly into their terminal without manually
translating fake values back to real ones.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Identity fields now support multiple entries per type:
- Add unlimited names (first, last, middle, nickname), emails,
usernames, hostnames, and phone numbers per profile
- "+ Add" button on each section, "x" to remove rows
- Names have a type selector (1st/Last/Mid/Nick)
README now includes:
- First-time setup walkthrough (step by step)
- Icon color legend (gray/black/blue/red)
- Keyboard shortcuts table
- Note that extension does nothing until configured
Also bumps version to 0.3.0 for Firefox re-signing.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
The previous approach (source .env && npm run sign:firefox) failed
because npm subshells don't inherit env vars consistently, and
quoted values in .env weren't being stripped.
New approach: sign-firefox.sh reads .env itself, strips quotes,
and passes --api-key/--api-secret directly to web-ext sign.
Also renames web-ext-config.js → web-ext-config.cjs to fix the
deprecation warning, and bumps package.json version to 0.2.0.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
Add package.json with web-ext dev dependency and npm scripts for
building, linting, signing, and running the Firefox extension.
Signing produces a self-hosted .xpi that persists across restarts
without needing the Mozilla store.
https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw