Persisted PII without encryption: status message + expiry and unsent composer drafts sit in plaintext localStorage #204

Closed
opened 2026-09-17 23:25:32 -04:00 by jared · 1 comment
Owner

Migrated from LOTUS_TODO.md on 2026-09-17 (the file is now reference-only).

Persisted PII without encryption: user status message + expiry (Profile.tsx), unsent composer drafts (RoomInput.tsx). Leak risk on shared devices.

Access-token storage (N97) is tracked in #105.

_Migrated from `LOTUS_TODO.md` on 2026-09-17 (the file is now reference-only)._ **Persisted PII without encryption:** user status message + expiry (`Profile.tsx`), unsent composer drafts (`RoomInput.tsx`). Leak risk on shared devices. Access-token storage (N97) is tracked in #105.
jared added the priority: lowsecurityarea: auth-session labels 2026-09-17 23:25:32 -04:00
Author
Owner

Reviewed against the current code:

  • Unsent composer drafts (draft-msg-*) — already swept on logout since #41 (clearMsgDrafts in state/plaintextCaches.ts).
  • Status message + expiry (lotus-status-msg-* / lotus-status-expiry-*) — were deliberately preserved across logout. Changed in e078a2cc (privacy: wipe the local status-message mirror on logout): they are PII and the authoritative copy lives in server presence, so a re-login loses nothing.

While there: the unit test's localStorage mock didn't enumerate keys, so the prefix sweeps were never actually exercised (the old "draft preserved" assertion passed vacuously). It now mirrors Object.keys like the real Storage object and asserts drafts + status are wiped and the session key is untouched.

What remains is inherent: while signed in, drafts/status sit in plaintext just like the message cache in IndexedDB — encrypting them with a key that itself has to live in the same origin storage would only be theatre. Closing.

Reviewed against the current code: - **Unsent composer drafts** (`draft-msg-*`) — already swept on logout since #41 (`clearMsgDrafts` in `state/plaintextCaches.ts`). - **Status message + expiry** (`lotus-status-msg-*` / `lotus-status-expiry-*`) — were deliberately preserved across logout. Changed in `e078a2cc` (`privacy: wipe the local status-message mirror on logout`): they are PII and the authoritative copy lives in server presence, so a re-login loses nothing. While there: the unit test's localStorage mock didn't enumerate keys, so the prefix sweeps were never actually exercised (the old "draft preserved" assertion passed vacuously). It now mirrors `Object.keys` like the real Storage object and asserts drafts + status are wiped and the session key is untouched. What remains is inherent: while signed in, drafts/status sit in plaintext just like the message cache in IndexedDB — encrypting them with a key that itself has to live in the same origin storage would only be theatre. Closing.
jared closed this issue 2026-09-19 13:38:50 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#204