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
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+24
-11
@@ -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,
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<label>Max log entries</label>
|
||||
<p class="setting-desc">Number of activity log entries to keep</p>
|
||||
</div>
|
||||
<input type="number" id="maxLogEntries" class="input-small" min="10" max="1000" value="200">
|
||||
<input type="number" id="maxLogEntries" class="input-small" min="10" max="1000" value="100">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<span>🔒</span> Sync Encryption
|
||||
</h3>
|
||||
<p class="section-desc" style="margin-bottom:10px">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div id="syncEncryptionSetup">
|
||||
|
||||
+22
-6
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user