A tiny m.room.retention value permanently deletes opted-in users' messages and hides the room's history for everyone (the 10-minute safety floor is never applied) #298

Closed
opened 2026-10-04 21:06:15 -04:00 by jared · 0 comments
Owner

What happens
src/app/utils/retention.ts defines a safety floor:

// Floor to avoid foot-guns (an admin fat-fingering a tiny value nuking a room).
export const RETENTION_MIN_MS = 10 * 60 * 1000;

Nothing uses it. getRoomRetentionMs() returns any positive max_lifetime as-is. The Lotus room-settings UI only offers 1 day / 1 week / 1 month, but m.room.retention can be set to anything from Element, devtools, a bot or the API by anyone with state power (moderators by default).

Verified (local Synapse, Playwright): a room with 4 of my messages and 1 of Bob's. I had Settings → General → Enforce Message Retention on. Then m.room.retention {"max_lifetime": 1000} was set (1 second).

  • The timeline goes blank for every Lotus user: 10 rendered messages before, 0 after (RoomTimeline hides anything older than the window).
  • Within 40 s, all 4 of my messages were redacted on the server. RetentionSweeper (src/app/pages/client/ClientNonUIFeatures.tsx) checks every 30 s and calls mx.redactEvent(..., { reason: 'expired' }) for every expired own message in the loaded timeline. That's permanent.

A typo (1000 instead of 86400000), or one malicious or compromised moderator, wipes the messages of every member who opted in, across every device where they're signed in. Removing the event afterwards doesn't bring anything back.

Introduced by 82e52e1b (2026-07-03, Disappearing Messages / MSC1763); the floor constant arrived without a caller.

Fix

  • Apply the floor in getRoomRetentionMs, ignoring values below RETENTION_MIN_MS (and maybe above it too, e.g. anything under 1 hour, for the auto-delete path).
  • Have the sweeper refuse to act on a retention event younger than, say, 24 h (a grace period so a mistake can be reverted before anything is deleted), or ask the user before the first bulk deletion in a room.
  • Show the room's retention in the room header or settings so members can see it.

Acceptance criteria

  • max_lifetime < 10 min is treated as off: nothing hidden, nothing redacted.
  • A new retention event never causes deletions in its first 24 h.
  • Unit tests for getRoomRetentionMs with tiny values, plus a Playwright regression test based on this repro.

Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

**What happens** `src/app/utils/retention.ts` defines a safety floor: ```ts // Floor to avoid foot-guns (an admin fat-fingering a tiny value nuking a room). export const RETENTION_MIN_MS = 10 * 60 * 1000; ``` **Nothing uses it.** `getRoomRetentionMs()` returns any positive `max_lifetime` as-is. The Lotus room-settings UI only offers 1 day / 1 week / 1 month, but `m.room.retention` can be set to anything from Element, devtools, a bot or the API by **anyone with state power (moderators by default)**. **Verified** (local Synapse, Playwright): a room with 4 of my messages and 1 of Bob's. I had Settings → General → **Enforce Message Retention** on. Then `m.room.retention {"max_lifetime": 1000}` was set (1 second). - **The timeline goes blank for every Lotus user:** 10 rendered messages before, **0** after (`RoomTimeline` hides anything older than the window). - **Within 40 s, all 4 of my messages were redacted on the server.** `RetentionSweeper` (src/app/pages/client/ClientNonUIFeatures.tsx) checks every 30 s and calls `mx.redactEvent(..., { reason: 'expired' })` for every expired own message in the loaded timeline. That's permanent. A typo (`1000` instead of `86400000`), or one malicious or compromised moderator, wipes the messages of every member who opted in, across every device where they're signed in. Removing the event afterwards doesn't bring anything back. **Introduced by** 82e52e1b (2026-07-03, Disappearing Messages / MSC1763); the floor constant arrived without a caller. **Fix** - Apply the floor in `getRoomRetentionMs`, ignoring values below `RETENTION_MIN_MS` (and maybe above it too, e.g. anything under 1 hour, for the auto-delete path). - Have the sweeper refuse to act on a retention event younger than, say, 24 h (a grace period so a mistake can be reverted before anything is deleted), or ask the user before the first bulk deletion in a room. - Show the room's retention in the room header or settings so members can see it. **Acceptance criteria** - `max_lifetime` < 10 min is treated as off: nothing hidden, nothing redacted. - A new retention event never causes deletions in its first 24 h. - Unit tests for `getRoomRetentionMs` with tiny values, plus a Playwright regression test based on this repro. --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the bugpriority: higharea: messaging labels 2026-10-04 21:06:15 -04:00
jared closed this issue 2026-10-16 22:48:19 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#298