From 3420299f515a4522448395026ff5d7f8aef42a01 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 20:55:12 +0000 Subject: [PATCH 1/3] Fix cross-browser GitHub Gist sync when encryption is enabled Two bugs prevented sync from working between different browsers sharing the same GitHub token and encryption password: 1. pullFromGist failed immediately with "No Gist ID stored" on any browser that had not previously pushed, because ss_gist_id lives in browser.storage.local (isolated per browser). Fix: when no local Gist ID is found, search the authenticated user's Gists for one containing silent-send-sync.json and cache the result. 2. When the pulled data was encrypted with a different salt (each browser generates its own random salt on setup), pullFromGist returned needsAuth:true but the UI never set window.__ssPendingSyncImport, so the auth prompt fell through to reverifyWithPassword instead of authenticateForSync, and the pull was never retried after the user entered their password. Fix: set window.__ssPendingSyncImport before showing the auth prompt for both Gist pull and custom-URL pull, matching how sync-code import already handled this flow. https://claude.ai/code/session_01QJnEnLfbXKR5FSCQ3Qfs53 --- src/lib/sync.js | 34 ++++++++++++++++++++++++++++++++-- src/options/options.js | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/lib/sync.js b/src/lib/sync.js index dc40c40..9785714 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -752,12 +752,42 @@ const SilentSendSync = { } }, + /** + * Search the authenticated user's Gists for one containing silent-send-sync.json. + * Returns the Gist ID string, or null if not found. + */ + async _findSyncGistId(token) { + try { + let page = 1; + while (page <= 5) { + const resp = await fetch(`https://api.github.com/gists?per_page=100&page=${page}`, { + headers: { Authorization: `token ${token}` }, + }); + if (!resp.ok) return null; + const gists = await resp.json(); + if (!gists.length) break; + for (const gist of gists) { + if (gist.files?.['silent-send-sync.json']) return gist.id; + } + if (gists.length < 100) break; + page++; + } + return null; + } catch { return null; } + }, + async pullFromGist(token) { if (!token) return { success: false, reason: 'No GitHub token provided.' }; try { const stored = await api.storage.local.get('ss_gist_id'); - const gistId = stored.ss_gist_id; - if (!gistId) return { success: false, reason: 'No Gist ID stored. Push first.' }; + let gistId = stored.ss_gist_id; + + if (!gistId) { + // No locally-stored Gist ID — search the account for one + gistId = await this._findSyncGistId(token); + if (!gistId) return { success: false, reason: 'No sync Gist found. Push from the source browser first.' }; + await api.storage.local.set({ ss_gist_id: gistId }); + } const resp = await fetch(`https://api.github.com/gists/${gistId}`, { headers: { Authorization: `token ${token}` }, diff --git a/src/options/options.js b/src/options/options.js index c610ca3..78d0d4d 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -189,7 +189,24 @@ document.addEventListener('DOMContentLoaded', async () => { const r = await SilentSendSync.pullFromGist(token); if (r.needsAuth) { setGistSyncStatus('Authentication required to decrypt.', 'warn'); - showSyncAuthPrompt(); + window.__ssPendingSyncImport = async () => { + const r2 = await SilentSendSync.pullFromGist(token); + if (r2.needsAuth) { + setGistSyncStatus('Authentication failed — wrong password?', 'error'); + } else if (!r2.success) { + setGistSyncStatus('Pull failed: ' + r2.reason, 'error'); + } else if (r2.imported) { + setGistSyncStatus(`Pulled (${r2.time}). Refreshing…`, 'ok'); + mappings = await Storage.getMappings(); + settings = await Storage.getSettings(); + renderMappings(); + renderDomains(); + renderLog(); + } else { + setGistSyncStatus('Already up to date.', 'ok'); + } + }; + showSyncAuthPrompt('decrypt'); } else if (!r.success) { setGistSyncStatus('Pull failed: ' + r.reason, 'error'); } else if (r.imported) { @@ -229,7 +246,24 @@ document.addEventListener('DOMContentLoaded', async () => { const r = await SilentSendSync.pullFromUrl({ url, headers }); if (r.needsAuth) { setUrlSyncStatus('Authentication required to decrypt.', 'warn'); - showSyncAuthPrompt(); + window.__ssPendingSyncImport = async () => { + const r2 = await SilentSendSync.pullFromUrl({ url, headers }); + if (r2.needsAuth) { + setUrlSyncStatus('Authentication failed — wrong password?', 'error'); + } else if (!r2.success) { + setUrlSyncStatus('Pull failed: ' + r2.reason, 'error'); + } else if (r2.imported) { + setUrlSyncStatus(`Pulled (${r2.time}). Refreshing…`, 'ok'); + mappings = await Storage.getMappings(); + settings = await Storage.getSettings(); + renderMappings(); + renderDomains(); + renderLog(); + } else { + setUrlSyncStatus('Already up to date.', 'ok'); + } + }; + showSyncAuthPrompt('decrypt'); } else if (!r.success) { setUrlSyncStatus('Pull failed: ' + r.reason, 'error'); } else if (r.imported) { From 8539c7ba9be8106979b4329ba3d237e68f5d1d7f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 21:20:46 +0000 Subject: [PATCH 2/3] Bump version to 0.9.32 https://claude.ai/code/session_01QJnEnLfbXKR5FSCQ3Qfs53 --- manifest.firefox.json | 2 +- manifest.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.firefox.json b/manifest.firefox.json index f84fde0..15b8772 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.9.31", + "version": "0.9.32", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "browser_specific_settings": { "gecko": { diff --git a/manifest.json b/manifest.json index e18a5c1..1d4c40c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.9.31", + "version": "0.9.32", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "permissions": [ "storage", diff --git a/package.json b/package.json index d69c4aa..2eb1169 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silent-send", - "version": "0.9.31", + "version": "0.9.32", "private": true, "license": "MIT", "description": "Browser extension that substitutes personal data before sending to AI services", From 6a962e927ba5e7edb9783d09f7f3b627f2afab0a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 21:21:16 +0000 Subject: [PATCH 3/3] Add .version-bump-undo to .gitignore Local scratch file created by bump-version.sh; no need to track it. https://claude.ai/code/session_01QJnEnLfbXKR5FSCQ3Qfs53 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 364cb9c..bbfa1fc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules/ dist/ .env web-ext-artifacts/ +.version-bump-undo