From a3904843ba545b8a4214e2c0ae529462f0afd165 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Mar 2026 04:17:50 +0000 Subject: [PATCH] security: require encryption for all sync channels, reduce activity log to 100 Sync operations (browser sync, Gist, custom URL, sync code) now refuse to operate without encryption enabled. Disabling encryption also turns off all active sync channels. Activity log cap reduced from 200 to 100 entries for both storage and display. https://claude.ai/code/session_01KF4i7Ra7zCEDskxDBaNtcT --- src/content/injector.js | 2 +- src/lib/storage.js | 2 +- src/lib/sync.js | 35 ++++++++++++++++++++++++----------- src/options/options.html | 4 ++-- src/options/options.js | 28 ++++++++++++++++++++++------ 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/content/injector.js b/src/content/injector.js index 7bf78c4..9269750 100644 --- a/src/content/injector.js +++ b/src/content/injector.js @@ -105,7 +105,7 @@ url: location.href, }); // Trim - if (log.length > 200) log.length = 200; + if (log.length > 100) log.length = 100; await api.storage.local.set({ ss_activity_log: log }); } } diff --git a/src/lib/storage.js b/src/lib/storage.js index 9a60b2d..61d11c3 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -38,7 +38,7 @@ const DEFAULT_SETTINGS = { autoDetect: true, autoRedactDetected: true, autoAddDetected: true, - maxLogEntries: 200, + maxLogEntries: 100, customDomains: [], categories: ['name', 'email', 'phone', 'address', 'ssn', 'dob', 'domain', 'password', 'general'], browserSync: false, diff --git a/src/lib/sync.js b/src/lib/sync.js index 80c743a..ef75c6f 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -12,8 +12,9 @@ * 5. Custom HTTP endpoint — any URL supporting GET + PUT (WebDAV, * self-hosted server, cloud function, etc.). * - * Encryption: all sync channels can optionally encrypt data with a - * password (AES-256-GCM) and/or require TOTP verification. + * Encryption: all sync channels REQUIRE encryption with a password + * (AES-256-GCM) and/or TOTP verification. Syncing without encryption + * is not permitted — users must set up encryption before enabling sync. * Authentication is cached with a configurable TTL so the user only * needs to authenticate when the cache expires and new data exists. * @@ -370,6 +371,9 @@ const SilentSendSync = { const StorageModule = (await import('./storage.js')).default; await StorageModule.decryptAllData(); + // Disable all sync channels since encryption is mandatory for sync + await StorageModule.saveSettings({ browserSync: false }); + await api.storage.local.remove('ss_sync_encryption'); await SilentSendCrypto.clearCachedKey(); await SilentSendCrypto.clearWebAuthnCredential(); @@ -415,7 +419,7 @@ const SilentSendSync = { */ async _encryptForSync(data) { const config = await this._getSyncEncryption(); - if (!config?.enabled) return { data, encrypted: false }; + if (!config?.enabled) return { data: null, encrypted: false, needsEncryption: true }; const keyInfo = await this._getEncryptionKey(); if (!keyInfo) { @@ -507,12 +511,15 @@ const SilentSendSync = { async exportSyncCode() { const data = await this._getAllData(); - // Encrypt if enabled + // Encrypt (mandatory) const result = await this._encryptForSync(data); + if (result.needsEncryption) { + return { needsEncryption: true }; + } if (result.needsAuth) { return { needsAuth: true }; } - const payload = result.data || data; + const payload = result.data; const json = JSON.stringify(payload); return btoa(unescape(encodeURIComponent(json))); @@ -568,10 +575,10 @@ const SilentSendSync = { try { const data = await this._getAllData(); - // Encrypt if enabled + // Encrypt (mandatory) const result = await this._encryptForSync(data); - if (result.needsAuth) return; // silently skip — will sync on next auth - const payload = result.data || data; + if (result.needsEncryption || result.needsAuth) return; // skip — encryption required + const payload = result.data; const json = JSON.stringify(payload); @@ -637,12 +644,15 @@ const SilentSendSync = { try { const data = await this._getAllData(); - // Encrypt if enabled + // Encrypt (mandatory) const encResult = await this._encryptForSync(data); + if (encResult.needsEncryption) { + return { success: false, needsEncryption: true, reason: 'Encryption must be enabled before syncing.' }; + } if (encResult.needsAuth) { return { success: false, needsAuth: true, reason: 'Authentication required.' }; } - const payload = encResult.data || data; + const payload = encResult.data; const content = JSON.stringify(payload, null, 2); const stored = await api.storage.local.get('ss_gist_id'); @@ -738,10 +748,13 @@ const SilentSendSync = { const data = await this._getAllData(); const encResult = await this._encryptForSync(data); + if (encResult.needsEncryption) { + return { success: false, needsEncryption: true, reason: 'Encryption must be enabled before syncing.' }; + } if (encResult.needsAuth) { return { success: false, needsAuth: true, reason: 'Authentication required.' }; } - const payload = encResult.data || data; + const payload = encResult.data; const resp = await fetch(url, { method, diff --git a/src/options/options.html b/src/options/options.html index ea8ce0a..f16ba31 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -92,7 +92,7 @@

Number of activity log entries to keep

- + @@ -106,7 +106,7 @@ 🔒 Sync Encryption

- Encrypt your sync data with a password, TOTP, or both. Authentication is only required when new data arrives and your cached key has expired. + Encryption is required for all sync channels. Set up a password (and optionally TOTP) before enabling any sync method. Authentication is only required when new data arrives and your cached key has expired.

diff --git a/src/options/options.js b/src/options/options.js index fbf0b11..fcc17ac 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -30,7 +30,7 @@ document.addEventListener('DOMContentLoaded', async () => { $('#autoDetect').checked = settings.autoDetect !== false; $('#autoRedactDetected').checked = settings.autoRedactDetected !== false; $('#autoAddDetected').checked = settings.autoAddDetected !== false; - $('#maxLogEntries').value = settings.maxLogEntries || 200; + $('#maxLogEntries').value = settings.maxLogEntries || 100; $('#browserSync').checked = settings.browserSync === true; renderMappings(); @@ -51,17 +51,28 @@ document.addEventListener('DOMContentLoaded', async () => { // --- Sync section --- $('#browserSync').addEventListener('change', async (e) => { - await Storage.saveSettings({ browserSync: e.target.checked }); if (e.target.checked) { + const encEnabled = await SilentSendSync.isEncryptionEnabled(); + if (!encEnabled) { + e.target.checked = false; + setSyncStatus('Encryption must be enabled before syncing. Set up encryption first.', 'error'); + return; + } + await Storage.saveSettings({ browserSync: true }); await SilentSendSync.pushToSyncStorage(); setSyncStatus('Browser account sync enabled. Your settings will sync automatically.', 'ok'); } else { + await Storage.saveSettings({ browserSync: false }); setSyncStatus('Browser account sync disabled.', 'neutral'); } }); $('#btnGenerateSyncCode').addEventListener('click', async () => { const code = await SilentSendSync.exportSyncCode(); + if (code?.needsEncryption) { + setSyncStatus('Encryption must be enabled before syncing. Set up encryption first.', 'error'); + return; + } if (code?.needsAuth) { setSyncStatus('Authentication required to encrypt sync code.', 'warn'); showSyncAuthPrompt(); @@ -168,7 +179,9 @@ document.addEventListener('DOMContentLoaded', async () => { if (!token) { setGistSyncStatus('Enter your GitHub PAT first.', 'warn'); return; } setGistSyncStatus('Pushing…', 'neutral'); const r = await SilentSendSync.pushToGist(token); - if (r.needsAuth) { + if (r.needsEncryption) { + setGistSyncStatus('Encryption must be enabled before syncing.', 'error'); + } else if (r.needsAuth) { setGistSyncStatus('Authentication required to encrypt.', 'warn'); showSyncAuthPrompt(); } else if (r.success) { @@ -207,7 +220,9 @@ document.addEventListener('DOMContentLoaded', async () => { const headers = parseHeadersField($('#customSyncHeaders').value); setUrlSyncStatus('Pushing…', 'neutral'); const r = await SilentSendSync.pushToUrl({ url, headers }); - if (r.needsAuth) { + if (r.needsEncryption) { + setUrlSyncStatus('Encryption must be enabled before syncing.', 'error'); + } else if (r.needsAuth) { setUrlSyncStatus('Authentication required to encrypt.', 'warn'); showSyncAuthPrompt(); } else if (r.success) { @@ -282,7 +297,7 @@ document.addEventListener('DOMContentLoaded', async () => { }); $('#maxLogEntries').addEventListener('change', async (e) => { - await Storage.saveSettings({ maxLogEntries: parseInt(e.target.value, 10) || 200 }); + await Storage.saveSettings({ maxLogEntries: parseInt(e.target.value, 10) || 100 }); }); // Add mapping @@ -952,8 +967,9 @@ async function initSyncEncryptionUI() { $('#btnDisableEncryption').addEventListener('click', async () => { if (!window.confirm('Disable sync encryption? Existing encrypted sync data will become unreadable.')) return; await SilentSendSync.disableEncryption(); + $('#browserSync').checked = false; showEncryptionNotConfigured(); - setSyncEncStatus('Encryption disabled.', 'neutral'); + setSyncEncStatus('Encryption disabled. All sync channels have been turned off.', 'neutral'); }); // Change password