feat: encrypted sync with password/TOTP/WebAuthn + smart reveal

Sync encryption:
- AES-256-GCM encryption for all sync channels (browser sync, gist,
  custom URL, folder sync, sync codes)
- Password with optional TOTP (RFC 6238) second factor
- Configurable auth TTL: session, 30/90/180/365 days, or never
- CryptoKey cached in IndexedDB — auth only needed when cache expires
  AND new data exists (lastModified check runs before auth prompt)
- WebAuthn (biometric/PIN) as low-friction re-authentication gate
- Full options UI for setup, password change, and inline auth prompt

Smart reveal:
- Track which substitute values were actually sent outbound per session
- Reveal mode only replaces values that were genuinely substituted,
  preventing false positives (e.g. AI using the word "user" won't be
  replaced with a real username that maps to "user")

https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
This commit is contained in:
Claude
2026-03-26 18:08:21 +00:00
parent 8eee6aa922
commit 8198620b8a
5 changed files with 1194 additions and 136 deletions
+86
View File
@@ -100,6 +100,92 @@
<h2>Sync Between Browsers</h2>
<p class="section-desc">Keep your identities, mappings, and settings in sync across browsers.</p>
<!-- Sync Encryption -->
<div style="margin-bottom:20px;padding:12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
<h3 style="font-size:13px;font-weight:600;margin:0 0 8px;display:flex;align-items:center;gap:6px">
<span>&#128274;</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.
</p>
<div id="syncEncryptionSetup">
<!-- Shown when encryption is NOT set up -->
<div id="encryptionNotConfigured">
<div style="display:flex;flex-direction:column;gap:8px">
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:end">
<div style="flex:1;min-width:160px">
<label style="font-size:11px;color:#6b7280;display:block;margin-bottom:2px">Password</label>
<input type="password" id="syncEncPassword" placeholder="Encryption password" autocomplete="new-password"
style="width:100%;box-sizing:border-box;font-size:12px;padding:5px 8px;border:1px solid #d1d5db;border-radius:6px">
</div>
<div style="flex:1;min-width:160px">
<label style="font-size:11px;color:#6b7280;display:block;margin-bottom:2px">Confirm Password</label>
<input type="password" id="syncEncPasswordConfirm" placeholder="Confirm password" autocomplete="new-password"
style="width:100%;box-sizing:border-box;font-size:12px;padding:5px 8px;border:1px solid #d1d5db;border-radius:6px">
</div>
</div>
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center">
<label style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer">
<input type="checkbox" id="syncEncTOTP"> Enable TOTP (authenticator app)
</label>
<label style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer" id="syncEncWebAuthnLabel">
<input type="checkbox" id="syncEncWebAuthn"> Biometric/PIN re-auth
</label>
<div style="display:flex;align-items:center;gap:4px">
<label style="font-size:12px;white-space:nowrap">Re-auth every</label>
<select id="syncEncTTL" style="font-size:12px;padding:3px 6px;border:1px solid #d1d5db;border-radius:4px">
<option value="0">Each session</option>
<option value="30">30 days</option>
<option value="90" selected>90 days</option>
<option value="180">180 days</option>
<option value="365">1 year</option>
<option value="-1">Never</option>
</select>
</div>
</div>
<div>
<button class="btn btn-primary" id="btnSetupEncryption">Enable Encryption</button>
</div>
</div>
</div>
<!-- Shown when encryption IS set up -->
<div id="encryptionConfigured" style="display:none">
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<span style="font-size:12px;color:#10b981;font-weight:500">&#10003; Encryption active</span>
<span id="encryptionInfo" style="font-size:11px;color:#6b7280"></span>
<button class="btn btn-sm" id="btnChangeEncPassword">Change Password</button>
<button class="btn btn-sm btn-danger" id="btnDisableEncryption">Disable</button>
</div>
</div>
<!-- Auth prompt (shown when key cache expired and sync needs decryption) -->
<div id="syncAuthPrompt" style="display:none;margin-top:10px;padding:10px;background:#fffbeb;border:1px solid #fcd34d;border-radius:6px">
<p style="font-size:12px;font-weight:500;margin:0 0 8px;color:#92400e">Authentication required — new sync data is available</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:end">
<input type="password" id="syncAuthPassword" placeholder="Password" autocomplete="current-password"
style="flex:1;min-width:120px;font-size:12px;padding:5px 8px;border:1px solid #d1d5db;border-radius:6px">
<input type="text" id="syncAuthTOTP" placeholder="TOTP code" autocomplete="one-time-code" inputmode="numeric" maxlength="6"
style="width:80px;font-size:12px;padding:5px 8px;border:1px solid #d1d5db;border-radius:6px;display:none">
<button class="btn btn-primary btn-sm" id="btnSyncAuth">Unlock</button>
<button class="btn btn-sm" id="btnSyncAuthBiometric" style="display:none">Biometric</button>
</div>
<div id="syncAuthStatus" style="font-size:11px;margin-top:4px;min-height:14px"></div>
</div>
<!-- TOTP setup result -->
<div id="totpSetupResult" style="display:none;margin-top:10px;padding:10px;background:#f0fdf4;border:1px solid #86efac;border-radius:6px">
<p style="font-size:12px;font-weight:500;margin:0 0 6px;color:#166534">TOTP Secret — save this in your authenticator app:</p>
<code id="totpSecretDisplay" style="font-size:13px;font-weight:600;letter-spacing:2px;display:block;margin-bottom:6px;word-break:break-all"></code>
<p style="font-size:11px;color:#6b7280;margin:0">Or use the otpauth URI: <code id="totpURIDisplay" style="font-size:10px;word-break:break-all"></code></p>
<button class="btn btn-sm" id="btnDismissTOTP" style="margin-top:8px">Done — I've saved it</button>
</div>
</div>
<div id="syncEncStatus" style="font-size:12px;margin-top:6px;min-height:14px"></div>
</div>
<div class="setting-row">
<div>
<label>Browser account sync</label>