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,
|
url: location.href,
|
||||||
});
|
});
|
||||||
// Trim
|
// Trim
|
||||||
if (log.length > 200) log.length = 200;
|
if (log.length > 100) log.length = 100;
|
||||||
await api.storage.local.set({ ss_activity_log: log });
|
await api.storage.local.set({ ss_activity_log: log });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ const DEFAULT_SETTINGS = {
|
|||||||
autoDetect: true,
|
autoDetect: true,
|
||||||
autoRedactDetected: true,
|
autoRedactDetected: true,
|
||||||
autoAddDetected: true,
|
autoAddDetected: true,
|
||||||
maxLogEntries: 200,
|
maxLogEntries: 100,
|
||||||
customDomains: [],
|
customDomains: [],
|
||||||
categories: ['name', 'email', 'phone', 'address', 'ssn', 'dob', 'domain', 'password', 'general'],
|
categories: ['name', 'email', 'phone', 'address', 'ssn', 'dob', 'domain', 'password', 'general'],
|
||||||
browserSync: false,
|
browserSync: false,
|
||||||
|
|||||||
+24
-11
@@ -12,8 +12,9 @@
|
|||||||
* 5. Custom HTTP endpoint — any URL supporting GET + PUT (WebDAV,
|
* 5. Custom HTTP endpoint — any URL supporting GET + PUT (WebDAV,
|
||||||
* self-hosted server, cloud function, etc.).
|
* self-hosted server, cloud function, etc.).
|
||||||
*
|
*
|
||||||
* Encryption: all sync channels can optionally encrypt data with a
|
* Encryption: all sync channels REQUIRE encryption with a password
|
||||||
* password (AES-256-GCM) and/or require TOTP verification.
|
* (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
|
* Authentication is cached with a configurable TTL so the user only
|
||||||
* needs to authenticate when the cache expires and new data exists.
|
* needs to authenticate when the cache expires and new data exists.
|
||||||
*
|
*
|
||||||
@@ -370,6 +371,9 @@ const SilentSendSync = {
|
|||||||
const StorageModule = (await import('./storage.js')).default;
|
const StorageModule = (await import('./storage.js')).default;
|
||||||
await StorageModule.decryptAllData();
|
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 api.storage.local.remove('ss_sync_encryption');
|
||||||
await SilentSendCrypto.clearCachedKey();
|
await SilentSendCrypto.clearCachedKey();
|
||||||
await SilentSendCrypto.clearWebAuthnCredential();
|
await SilentSendCrypto.clearWebAuthnCredential();
|
||||||
@@ -415,7 +419,7 @@ const SilentSendSync = {
|
|||||||
*/
|
*/
|
||||||
async _encryptForSync(data) {
|
async _encryptForSync(data) {
|
||||||
const config = await this._getSyncEncryption();
|
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();
|
const keyInfo = await this._getEncryptionKey();
|
||||||
if (!keyInfo) {
|
if (!keyInfo) {
|
||||||
@@ -507,12 +511,15 @@ const SilentSendSync = {
|
|||||||
async exportSyncCode() {
|
async exportSyncCode() {
|
||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
// Encrypt if enabled
|
// Encrypt (mandatory)
|
||||||
const result = await this._encryptForSync(data);
|
const result = await this._encryptForSync(data);
|
||||||
|
if (result.needsEncryption) {
|
||||||
|
return { needsEncryption: true };
|
||||||
|
}
|
||||||
if (result.needsAuth) {
|
if (result.needsAuth) {
|
||||||
return { needsAuth: true };
|
return { needsAuth: true };
|
||||||
}
|
}
|
||||||
const payload = result.data || data;
|
const payload = result.data;
|
||||||
|
|
||||||
const json = JSON.stringify(payload);
|
const json = JSON.stringify(payload);
|
||||||
return btoa(unescape(encodeURIComponent(json)));
|
return btoa(unescape(encodeURIComponent(json)));
|
||||||
@@ -568,10 +575,10 @@ const SilentSendSync = {
|
|||||||
try {
|
try {
|
||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
// Encrypt if enabled
|
// Encrypt (mandatory)
|
||||||
const result = await this._encryptForSync(data);
|
const result = await this._encryptForSync(data);
|
||||||
if (result.needsAuth) return; // silently skip — will sync on next auth
|
if (result.needsEncryption || result.needsAuth) return; // skip — encryption required
|
||||||
const payload = result.data || data;
|
const payload = result.data;
|
||||||
|
|
||||||
const json = JSON.stringify(payload);
|
const json = JSON.stringify(payload);
|
||||||
|
|
||||||
@@ -637,12 +644,15 @@ const SilentSendSync = {
|
|||||||
try {
|
try {
|
||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
// Encrypt if enabled
|
// Encrypt (mandatory)
|
||||||
const encResult = await this._encryptForSync(data);
|
const encResult = await this._encryptForSync(data);
|
||||||
|
if (encResult.needsEncryption) {
|
||||||
|
return { success: false, needsEncryption: true, reason: 'Encryption must be enabled before syncing.' };
|
||||||
|
}
|
||||||
if (encResult.needsAuth) {
|
if (encResult.needsAuth) {
|
||||||
return { success: false, needsAuth: true, reason: 'Authentication required.' };
|
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 content = JSON.stringify(payload, null, 2);
|
||||||
const stored = await api.storage.local.get('ss_gist_id');
|
const stored = await api.storage.local.get('ss_gist_id');
|
||||||
@@ -738,10 +748,13 @@ const SilentSendSync = {
|
|||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
const encResult = await this._encryptForSync(data);
|
const encResult = await this._encryptForSync(data);
|
||||||
|
if (encResult.needsEncryption) {
|
||||||
|
return { success: false, needsEncryption: true, reason: 'Encryption must be enabled before syncing.' };
|
||||||
|
}
|
||||||
if (encResult.needsAuth) {
|
if (encResult.needsAuth) {
|
||||||
return { success: false, needsAuth: true, reason: 'Authentication required.' };
|
return { success: false, needsAuth: true, reason: 'Authentication required.' };
|
||||||
}
|
}
|
||||||
const payload = encResult.data || data;
|
const payload = encResult.data;
|
||||||
|
|
||||||
const resp = await fetch(url, {
|
const resp = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<label>Max log entries</label>
|
<label>Max log entries</label>
|
||||||
<p class="setting-desc">Number of activity log entries to keep</p>
|
<p class="setting-desc">Number of activity log entries to keep</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<span>🔒</span> Sync Encryption
|
<span>🔒</span> Sync Encryption
|
||||||
</h3>
|
</h3>
|
||||||
<p class="section-desc" style="margin-bottom:10px">
|
<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>
|
</p>
|
||||||
|
|
||||||
<div id="syncEncryptionSetup">
|
<div id="syncEncryptionSetup">
|
||||||
|
|||||||
+22
-6
@@ -30,7 +30,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
$('#autoDetect').checked = settings.autoDetect !== false;
|
$('#autoDetect').checked = settings.autoDetect !== false;
|
||||||
$('#autoRedactDetected').checked = settings.autoRedactDetected !== false;
|
$('#autoRedactDetected').checked = settings.autoRedactDetected !== false;
|
||||||
$('#autoAddDetected').checked = settings.autoAddDetected !== false;
|
$('#autoAddDetected').checked = settings.autoAddDetected !== false;
|
||||||
$('#maxLogEntries').value = settings.maxLogEntries || 200;
|
$('#maxLogEntries').value = settings.maxLogEntries || 100;
|
||||||
$('#browserSync').checked = settings.browserSync === true;
|
$('#browserSync').checked = settings.browserSync === true;
|
||||||
|
|
||||||
renderMappings();
|
renderMappings();
|
||||||
@@ -51,17 +51,28 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
// --- Sync section ---
|
// --- Sync section ---
|
||||||
$('#browserSync').addEventListener('change', async (e) => {
|
$('#browserSync').addEventListener('change', async (e) => {
|
||||||
await Storage.saveSettings({ browserSync: e.target.checked });
|
|
||||||
if (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();
|
await SilentSendSync.pushToSyncStorage();
|
||||||
setSyncStatus('Browser account sync enabled. Your settings will sync automatically.', 'ok');
|
setSyncStatus('Browser account sync enabled. Your settings will sync automatically.', 'ok');
|
||||||
} else {
|
} else {
|
||||||
|
await Storage.saveSettings({ browserSync: false });
|
||||||
setSyncStatus('Browser account sync disabled.', 'neutral');
|
setSyncStatus('Browser account sync disabled.', 'neutral');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#btnGenerateSyncCode').addEventListener('click', async () => {
|
$('#btnGenerateSyncCode').addEventListener('click', async () => {
|
||||||
const code = await SilentSendSync.exportSyncCode();
|
const code = await SilentSendSync.exportSyncCode();
|
||||||
|
if (code?.needsEncryption) {
|
||||||
|
setSyncStatus('Encryption must be enabled before syncing. Set up encryption first.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (code?.needsAuth) {
|
if (code?.needsAuth) {
|
||||||
setSyncStatus('Authentication required to encrypt sync code.', 'warn');
|
setSyncStatus('Authentication required to encrypt sync code.', 'warn');
|
||||||
showSyncAuthPrompt();
|
showSyncAuthPrompt();
|
||||||
@@ -168,7 +179,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
if (!token) { setGistSyncStatus('Enter your GitHub PAT first.', 'warn'); return; }
|
if (!token) { setGistSyncStatus('Enter your GitHub PAT first.', 'warn'); return; }
|
||||||
setGistSyncStatus('Pushing…', 'neutral');
|
setGistSyncStatus('Pushing…', 'neutral');
|
||||||
const r = await SilentSendSync.pushToGist(token);
|
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');
|
setGistSyncStatus('Authentication required to encrypt.', 'warn');
|
||||||
showSyncAuthPrompt();
|
showSyncAuthPrompt();
|
||||||
} else if (r.success) {
|
} else if (r.success) {
|
||||||
@@ -207,7 +220,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const headers = parseHeadersField($('#customSyncHeaders').value);
|
const headers = parseHeadersField($('#customSyncHeaders').value);
|
||||||
setUrlSyncStatus('Pushing…', 'neutral');
|
setUrlSyncStatus('Pushing…', 'neutral');
|
||||||
const r = await SilentSendSync.pushToUrl({ url, headers });
|
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');
|
setUrlSyncStatus('Authentication required to encrypt.', 'warn');
|
||||||
showSyncAuthPrompt();
|
showSyncAuthPrompt();
|
||||||
} else if (r.success) {
|
} else if (r.success) {
|
||||||
@@ -282,7 +297,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#maxLogEntries').addEventListener('change', async (e) => {
|
$('#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
|
// Add mapping
|
||||||
@@ -952,8 +967,9 @@ async function initSyncEncryptionUI() {
|
|||||||
$('#btnDisableEncryption').addEventListener('click', async () => {
|
$('#btnDisableEncryption').addEventListener('click', async () => {
|
||||||
if (!window.confirm('Disable sync encryption? Existing encrypted sync data will become unreadable.')) return;
|
if (!window.confirm('Disable sync encryption? Existing encrypted sync data will become unreadable.')) return;
|
||||||
await SilentSendSync.disableEncryption();
|
await SilentSendSync.disableEncryption();
|
||||||
|
$('#browserSync').checked = false;
|
||||||
showEncryptionNotConfigured();
|
showEncryptionNotConfigured();
|
||||||
setSyncEncStatus('Encryption disabled.', 'neutral');
|
setSyncEncStatus('Encryption disabled. All sync channels have been turned off.', 'neutral');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change password
|
// Change password
|
||||||
|
|||||||
Reference in New Issue
Block a user