Scheduled messages bypass encryption — plaintext m.room.message in E2EE rooms #7

Closed
opened 2026-09-12 01:50:47 -04:00 by jared · 0 comments
Owner

Severity: critical · Type: security · Confidence: high

Location: src/app/utils/scheduledMessages.ts:9-30, src/app/features/room/RoomInput.tsx:248 (showSchedule gating), src/app/features/room/ScheduleMessageModal.tsx:127

Problem

scheduleMessage sends the MSC4140 delayed event with a raw mx.http.authedRequest(Method.Put, '/rooms/{id}/send/m.room.message/{txn}', …, content). That path bypasses matrix-js-sdk's sendMessageencryptEventIfNeeded pipeline entirely, so in an encrypted room the message body is PUT to the homeserver as an unencrypted m.room.message and later fires into the timeline in the clear for everyone (and for the server). Nothing gates the Schedule button on room.hasEncryptionStateEvent()isEncrypted is computed at RoomInput.tsx:173 but only used for the unverified-device warning. The message body is additionally persisted to localStorage (cinny_scheduled_messages_v1) as plaintext.

How to trigger

Open an E2EE room → composer "Schedule message" → schedule anything. The delivered event is unencrypted.

Suggested fix

Either hide/disable schedule-send in encrypted rooms with a clear explanation, or encrypt the content with the crypto API before the delayed PUT and send m.room.encrypted (note that a pre-encrypted delayed event has megolm-rotation caveats — disabling is the safe short-term fix).


Filed from the September 2026 client audit (branch lotus @ 4bea4895).

**Severity:** critical · **Type:** security · **Confidence:** high **Location:** `src/app/utils/scheduledMessages.ts:9-30`, `src/app/features/room/RoomInput.tsx:248` (`showSchedule` gating), `src/app/features/room/ScheduleMessageModal.tsx:127` ### Problem `scheduleMessage` sends the MSC4140 delayed event with a raw `mx.http.authedRequest(Method.Put, '/rooms/{id}/send/m.room.message/{txn}', …, content)`. That path bypasses matrix-js-sdk's `sendMessage` → `encryptEventIfNeeded` pipeline entirely, so in an encrypted room the message body is PUT to the homeserver as an unencrypted `m.room.message` and later fires into the timeline in the clear for everyone (and for the server). Nothing gates the Schedule button on `room.hasEncryptionStateEvent()` — `isEncrypted` is computed at `RoomInput.tsx:173` but only used for the unverified-device warning. The message body is additionally persisted to `localStorage` (`cinny_scheduled_messages_v1`) as plaintext. ### How to trigger Open an E2EE room → composer "Schedule message" → schedule anything. The delivered event is unencrypted. ### Suggested fix Either hide/disable schedule-send in encrypted rooms with a clear explanation, or encrypt the content with the crypto API before the delayed PUT and send `m.room.encrypted` (note that a pre-encrypted delayed event has megolm-rotation caveats — disabling is the safe short-term fix). --- _Filed from the September 2026 client audit (branch `lotus` @ 4bea4895)._
jared added this to the Audit 2026-09 · P0 security & data loss milestone 2026-09-12 01:50:47 -04:00
jared added the bugpriority: criticalsecurityarea: messaging labels 2026-09-12 01:50:47 -04:00
jared self-assigned this 2026-09-12 01:50:47 -04:00
jared closed this issue 2026-09-12 02:14:51 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
2026-09-18
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#7