Skip to content

fix(crypto): re-sync rotated public keys + explicit key recovery choice#127

Merged
ralyodio merged 2 commits into
masterfrom
worktree-sms-lockdown
Jun 30, 2026
Merged

fix(crypto): re-sync rotated public keys + explicit key recovery choice#127
ralyodio merged 2 commits into
masterfrom
worktree-sms-lockdown

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Problem

Messaging a user who switched browsers silently failed (note-to-self worked). Root cause: after a key rotation / new device, the DB kept the user's old public key, so everyone encrypted to a dead key the new device couldn't decrypt.

Fixes

  1. needsKeySync() previously only checked whether any key existed in the DB, not whether it matched the local key — so a rotated key was never uploaded. Now compares DB key vs local key and re-syncs on mismatch. Server RPC already upserts (ON CONFLICT DO UPDATE), and the check self-corrects (no loop, heals legacy 768 keys too).

  2. EncryptionWarning now offers an explicit choice when a device has no keys:

    • Upload existing keys (recommended) → Settings import/restore, preserves history
    • Generate new keys → with a confirm() warning that past messages become permanently unreadable

    Previously it silently pointed to Settings, letting users land on fresh keys without understanding the cost.

oxlint clean. Note: old messages on a brand-new key still require restoring the old private key — by design (E2EE).

🤖 Generated with Claude Code

ralyodio and others added 2 commits June 30, 2026 23:07
The carrier 'file' message renders (and MessageAttachments fetches
/api/files/message/:id) before its files finish uploading, so the
initial fetch returns nothing. Because the effect only keyed on
[messageId], it never re-fetched and the attachment stayed invisible
until a full page refresh remounted the component.

Dispatch an 'attachments:updated' CustomEvent after the uploads +
loadMessages complete; MessageAttachments listens for its own messageId
and bumps a reloadKey to re-fetch and render the now-present files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rompt

Two related cross-device key problems:

1. needsKeySync() only checked whether *any* public key existed in the DB
   for the user, not whether it matched the local key. After switching
   browsers / rotating keys, the DB kept the OLD public key, so the sync was
   skipped and everyone kept encrypting to a dead key the new device can't
   read. Now it compares the DB key to the local key and re-syncs on mismatch
   (the server RPC already upserts via ON CONFLICT DO UPDATE).

2. EncryptionWarning now gives an explicit choice when a device has no keys:
   "Upload existing keys" (recommended — routes to Settings import/restore,
   preserves history) vs "Generate new keys" (with a confirm that past
   messages become unreadable). Previously it silently pointed at Settings,
   which let users end up on fresh keys without understanding the cost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio ralyodio merged commit bec1b87 into master Jun 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant