Matrix: a key that arrives late never rescues the message #162

Closed
opened 2026-09-10 15:45:10 -04:00 by Salastil · 1 comment
Owner

An event that cannot be decrypted when it arrives keeps its placeholder forever. The
code says so plainly — nobilis/src/backend/matrix/timeline.rs:108-114: "v1 shows an
honest placeholder rather than retry-on-later-key-arrival (request_room_key)".

This is the single most visible encryption failure in practice: join a room, scroll
back, and the history above your join is a wall of "cannot decrypt" that stays that way
even after the key backup restores the keys that would open it.

What is needed

  • request_room_key for an event that fails to decrypt, so the sender's other devices
    can forward the session.
  • Re-run decryption on the stored event when a key arrives — from a forward, from key
    backup (backup.rs:162), or from an import — and update the message in place, which
    update_message already does for edits.

Done looks like: a message that could not be read becomes readable when its key
turns up, without a restart.

An event that cannot be decrypted when it arrives keeps its placeholder forever. The code says so plainly — `nobilis/src/backend/matrix/timeline.rs:108-114`: "v1 shows an honest placeholder rather than retry-on-later-key-arrival (`request_room_key`)". This is the single most visible encryption failure in practice: join a room, scroll back, and the history above your join is a wall of "cannot decrypt" that stays that way even after the key backup restores the keys that would open it. **What is needed** - `request_room_key` for an event that fails to decrypt, so the sender's other devices can forward the session. - Re-run decryption on the stored event when a key arrives — from a forward, from key backup (`backup.rs:162`), or from an import — and update the message in place, which `update_message` already does for edits. **Done looks like:** a message that could not be read becomes readable when its key turns up, without a restart.
Salastil added the bugprotocol labels 2026-09-10 15:45:10 -04:00
Salastil removed the protocol label 2026-09-10 15:53:25 -04:00
Author
Owner

Done in nobilis c1d24c7.

Both halves, because both were needed. Asking — request_room_key to this account's other devices and the sender's, with the cancellation sent first so a room full of unreadable history asks once per Megolm session rather than once per message. And retrying, because a key can arrive without having been asked for: from a backup restore, an import, or another device deciding to share. The retry runs after each sync's crypto changes land — the one place all three have already arrived — and again on restore and import, where somebody is watching to see whether it worked.

What is held is the encrypted event, since that is the only thing that can be decrypted later; the placeholder is a rendering of a failure and cannot be turned back into a message.

Bounded at 500 per account, oldest dropped first. Joining a large encrypted room with no keys produces thousands at once, and a session that opens one message opens its neighbours — what anybody can see is a screen's worth.

Held across a disconnect on purpose: the messages are still unreadable and the keys may arrive on the next sync, so dropping them would make a reconnect fix nothing.

Only messages are rewritten. A reaction or an edit that could not be read is dropped rather than replayed — there is no placeholder on screen for either, and applying them now would apply them out of order.

Done in nobilis [c1d24c7](https://git.salastil.com/Salastil/nobilis/commit/c1d24c7). Both halves, because both were needed. **Asking** — `request_room_key` to this account's other devices and the sender's, with the cancellation sent first so a room full of unreadable history asks once per Megolm session rather than once per message. And **retrying**, because a key can arrive without having been asked for: from a backup restore, an import, or another device deciding to share. The retry runs after each sync's crypto changes land — the one place all three have already arrived — and again on restore and import, where somebody is watching to see whether it worked. What is held is the encrypted event, since that is the only thing that can be decrypted later; the placeholder is a rendering of a failure and cannot be turned back into a message. Bounded at 500 per account, oldest dropped first. Joining a large encrypted room with no keys produces thousands at once, and a session that opens one message opens its neighbours — what anybody can see is a screen's worth. Held across a disconnect on purpose: the messages are still unreadable and the keys may arrive on the next sync, so dropping them would make a reconnect fix nothing. Only messages are rewritten. A reaction or an edit that could not be read is dropped rather than replayed — there is no placeholder on screen for either, and applying them now would apply them out of order.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Salastil/moho#162