feat: at-rest encryption for all sensitive data + vault unlock flow
All sensitive data (identity, mappings, activity log) is now AES-256 encrypted in browser.storage.local when sync encryption is enabled. TOTP secret is also encrypted at rest using the derived key. Vault unlock flow: - On browser restart, extension detects locked state (encrypted data, no cached CryptoKey) and shows LOCK badge in red - Popup shows a full-screen unlock prompt with password field, optional TOTP, and biometric button - After unlock, background decrypts and broadcasts data to all tabs - Content scripts start with empty config when locked; receive decrypted config via vault:unlocked message after unlock - Injector skips encrypted blobs in storage change events Storage module changes: - _readSecure / _writeSecure transparently encrypt/decrypt - encryptExistingData() migrates plaintext → encrypted on setup - decryptAllData() restores plaintext when encryption is disabled - isLocked() checks for encrypted data + missing key https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
This commit is contained in:
@@ -153,6 +153,44 @@ const messageHandlers = {
|
||||
api.action.setBadgeBackgroundColor({ color: '#6b7280' });
|
||||
},
|
||||
|
||||
async 'get:locked-state'(_message, _sender, sendResponse) {
|
||||
const locked = await Storage.isLocked();
|
||||
sendResponse({ locked });
|
||||
},
|
||||
|
||||
async 'vault:unlocked'() {
|
||||
// User unlocked the vault — clear the LOCK badge and refresh icon
|
||||
api.action.setBadgeText({ text: '' });
|
||||
const settings = await Storage.getSettings();
|
||||
await updateIcon(settings);
|
||||
|
||||
// Now that we're unlocked, try syncing
|
||||
if (settings.browserSync) {
|
||||
await SilentSendSync.pullFromSyncStorage();
|
||||
}
|
||||
|
||||
// Read decrypted data via Storage module and send to all content scripts
|
||||
const mappings = await Storage.getMappings();
|
||||
const identity = await Storage.getIdentity();
|
||||
|
||||
const allPatterns = [...BUILTIN_URL_PATTERNS];
|
||||
const customDomains = settings.customDomains || [];
|
||||
for (const domain of customDomains) {
|
||||
allPatterns.push(domain + '/*');
|
||||
}
|
||||
for (const urlPattern of allPatterns) {
|
||||
const tabs = await api.tabs.query({ url: urlPattern }).catch(() => []);
|
||||
for (const tab of tabs) {
|
||||
api.tabs.sendMessage(tab.id, {
|
||||
type: 'vault:unlocked',
|
||||
mappings,
|
||||
identity,
|
||||
settings,
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async 'update:settings'(message) {
|
||||
await Storage.saveSettings(message.settings);
|
||||
|
||||
@@ -372,6 +410,14 @@ api.runtime.onInstalled.addListener(async () => {
|
||||
const settings = await Storage.getSettings();
|
||||
await updateIcon(settings);
|
||||
|
||||
// Check if extension is locked (encrypted data, no cached key)
|
||||
const locked = await Storage.isLocked();
|
||||
if (locked) {
|
||||
api.action.setBadgeText({ text: 'LOCK' });
|
||||
api.action.setBadgeBackgroundColor({ color: '#dc2626' });
|
||||
return; // don't try to sync while locked
|
||||
}
|
||||
|
||||
// Restore the SYN badge if the user hasn't opened Options since the last sync
|
||||
const stored = await api.storage.local.get('ss_sync_notification');
|
||||
if (stored.ss_sync_notification) {
|
||||
|
||||
Reference in New Issue
Block a user