Client audit 2026-09 — tracking index #89

Open
opened 2026-09-12 01:52:08 -04:00 by jared · 0 comments
Owner

September 2026 client audit — tracking issue

Eight parallel code-reading passes over the lotus branch (calls, messaging/threads, security/persistence, settings/appearance, notifications/desktop, navigation/moderation, accessibility/mobile, tooling) at commit 4bea4895. Every finding below was verified against the code before filing; items already open in LOTUS_TODO.md were deliberately not refiled.

Labelling scheme: each issue carries one priority: *, one type label (bug / security / performance / a11y / ux / tech-debt / dependencies / docs), and one area: * label. Milestones group them into P0 security & data loss (due 2026-09-26), High (due 2026-10-15) and Medium & Low (due 2026-11-30); critical issues also carry an individual due date of 2026-09-19.

Totals: 83 issues — 2 critical, 17 high, 31 medium, 33 low.

By area: messaging 18, calls 18, settings 8, appearance 7, build-ci 6, auth-session 5, navigation 5, threads 4, notifications 4, moderation 3, desktop 3, media 2.

Critical

  • #6 Image compression in an E2EE room uploads plaintext and breaks the event security messaging
  • #7 Scheduled messages bypass encryption — plaintext m.room.message in E2EE rooms security messaging

High

  • #8 Answering an incoming video call turns the camera on, ignoring the user setting bug calls
  • #9 Push-to-talk, the deafen hotkey and AFK auto-mute stop working outside the call room bug calls
  • #10 Bookmarks and reminders upload decrypted message text to server account data security messaging
  • #11 GIF picker sends unencrypted media into encrypted rooms security messaging
  • #12 Edits and reactions on a thread root are invisible in the thread panel bug threads
  • #13 Search date-range filter is silently ignored for unencrypted rooms bug messaging
  • #14 Persistent search index keeps decrypted plaintext of redacted messages and left rooms security messaging
  • #15 Room widgets never render — matrix-widget-api 1.17.0 isValidUrl is broken bug messaging
  • #16 OIDC token refresh force-reloads every other tab, dropping in-progress calls bug auth-session
  • #17 Renaming two rooms in quick succession silently loses the first rename bug settings
  • #18 Private user notes can lose typed text (store echo reverts draft; close within debounce drops save) bug settings
  • #19 Advanced Push Rule Editor silently strips the highlight tweak from mention rules bug notifications
  • #20 Favouriting/low-priority a room doesn't move it in the sidebar until an unrelated re-render bug navigation
  • #21 Timed room mute silently overwrites a manual notification-mode change made during the mute window bug navigation
  • #22 Policy List Viewer misses legacy Draupnir/Mjolnir policy event types, showing false "no rules found" bug moderation
  • #23 Call hotkeys: rebind has no denylist, modifiers ignored, Space swallowed on buttons, duplicate bindings allowed a11y calls
  • #24 npm audit: 11 advisories in runtime deps (pdfjs-dist JS-execution, react-router, sanitize-html, katex, uuid) dependencies build-ci

Medium

  • #25 Right-clicking the call button silently starts a camera-on video call ux calls
  • #26 Call-status bar bypasses the room screenshare/camera policy and the share confirmation bug calls
  • #27 PTT window blur/focus force-mutes unconditionally, revoking a manual unmute bug calls
  • #28 Incoming-call ringtone ignores quiet hours, DND and Focus Assist ux calls
  • #29 Logging out mid-call never hangs up, leaving a ghost RTC membership bug calls
  • #30 Voice-channel "Channel Full" limit is bypassed by joining from the sidebar bug calls
  • #31 Soundboard upload limit double-counts staged clips and reads a stale count bug calls
  • #32 Speaker set is reallocated on every call-state push, re-rendering the app-wide call bar performance calls
  • #33 Deafen button's aria-label is inverted relative to its tooltip a11y calls
  • #34 Thread read receipts ignore the "Hide Typing & Read Receipts" setting security threads
  • #35 Creating a poll from the thread composer posts it to the main timeline bug threads
  • #36 Scheduling from the composer drops formatting, mentions and the reply relation bug messaging
  • #37 Composer drafts are lost on page reload (only written on unmount) bug messaging
  • #38 Thread panel never shows read receipts (context provider is out of scope) bug threads
  • #39 Every text message subscribes to one global translation atom → timeline-wide re-render storms performance messaging
  • #40 Read-position recompute walks all joined members and re-renders the whole timeline performance messaging
  • #41 Composer drafts survive logout and are auto-hydrated into the next account's session security auth-session
  • #42 Settings are never synced across tabs — one tab's whole blob clobbers the other's bug settings
  • #43 Element Call iframe is same-origin, so its sandbox is a no-op security calls
  • #44 Shipped nginx/Netlify configs set no CSP, frame-ancestors or referrer policy security build-ci
  • #45 Logout's search-index wipe can report success while another tab still holds the DB security auth-session
  • #46 Avatar Decoration settings panel shows "None" on any fetch failure, not just true absence bug settings
  • #47 Profile field edits can be clobbered by a slow-loading initial fetch (pronouns, timezone, avatar decoration) bug settings
  • #48 Notification profile presets don't clear an active "Pause Notifications" snooze bug notifications
  • #49 "Deny" on a pending knock request has no error handling and can be shown to users who lack kick power bug moderation
  • #50 Room-history export shows outdated content and a garbled duplicate line for edited messages bug navigation
  • #51 Room invite link / QR code always uses the local homeserver as the only via-server, ignoring the shared via-server resolver bug navigation
  • #52 Primary accent color fails WCAG AA 4.5:1 as text in Midnight and Lotus Terminal Light themes a11y appearance
  • #53 Lotus-added UI bypasses the app's i18next mechanism entirely — always renders in English regardless of user locale ux settings
  • #54 Package.json engines version misalignment with README and CI tech-debt build-ci
  • #55 Fragile postinstall patch script for folds Icon tech-debt build-ci

Low

  • #56 "Focus camera" pins a participant with no way to unpin ux calls
  • #57 Soundboard clip blob URLs are never revoked and survive logout security calls
  • #58 No warning when closing the tab or reloading during an active call ux calls
  • #59 CallControl.setPipMode writes state nothing reads tech-debt calls
  • #60 In-iframe PTT/deafen key listeners are lost if the EC iframe reloads bug calls
  • #61 RoomMemberEvent.Membership listener on Room never fires (dead handler) bug messaging
  • #62 Sender-only search returns membership/state/reaction events as results ux messaging
  • #63 Forwarding an encrypted attachment into a plaintext room publishes its AES key security messaging
  • #64 Avatar-decoration slug from a remote profile is interpolated into a URL unvalidated security media
  • #65 KaTeX renders remote LaTeX with no maxSize/maxExpand limits security messaging
  • #66 Location messages load an OpenStreetMap iframe with no facade or auto-load gate security messaging
  • #67 Any OIDC login failure discards the cached dynamic client id bug auth-session
  • #68 GIF picker sends search terms and IP directly to Giphy, outside any privacy setting security messaging
  • #69 Cross-tab session sync is absent on the auth pages bug auth-session
  • #70 Documented "leading emoji rendered 1.15× larger" room-name feature is not implemented docs appearance
  • #71 Documented secondary glow/blink keyframes for animated backgrounds don't exist docs appearance
  • #72 Earth Day globe decoration risks sitting over the composer ux appearance
  • #73 Halloween fog-band layers keep willChange set even when motion is fully disabled performance appearance
  • #74 seasonalThemeOverride is the only enum setting with no persisted-value validation tech-debt settings
  • #75 Page Zoom input silently discards a typed-but-unconfirmed value on blur ux settings
  • #76 useTauriCallPower never releases the native keep-awake state on unmount bug desktop
  • #77 getNotificationType/main-timeline gate re-derives push rules on every event without memoization performance notifications
  • #78 notificationclick always focuses the first matching window client, not the one most relevant to the notification ux desktop
  • #79 Quick Reply's failure is swallowed with no user-visible feedback ux desktop
  • #80 LotusToastContainer toast auto-dismiss timer is not reset in a fixed slot, so a fast burst can hide a toast before it's read ux notifications
  • #81 Room-history export gives no indication that a message was media/an attachment bug media
  • #82 Room Activity Log can misclassify membership transitions for back-paginated (older) events bug moderation
  • #83 Room Insights ("stats") panel freezes at open-time data and never reflects new activity ux navigation
  • #84 SendingSpinClass and CallAvatarAnimation ignore prefers-reduced-motion a11y appearance
  • #85 CollapsibleBody's reduced-motion check is a one-time snapshot, not a live listener a11y messaging
  • #86 Terminal boot sequence has no touch/click dismiss and isn't hidden from assistive tech a11y appearance
  • #87 Dead code: unused useForceUpdate.js tech-debt build-ci
  • #88 syncDecorations.mjs fragile regex parsing tech-debt build-ci

Fix-first recommendation

  1. #6 and #7 (E2EE plaintext leaks in the composer) — small, contained fixes; ship together.
  2. #10, #11, #14 (plaintext leaving the device via account data, unencrypted GIF media, and the redaction gap in the persistent search index).
  3. #8 and #9 (camera on answer; PTT/AFK dead in picture-in-picture) — the two highest-impact call bugs.
  4. #16 (OIDC refresh reloading other tabs) before any multi-tab call workflows are relied on.
  5. #24 dependency bumps, with pdfjs-dist first.

Things checked and found sound

Sanitizer configuration (the sanitize-html advisories do not apply to this usage), KaTeX trust settings, all window.open / target=_blank sites, service-worker media auth scoping, session storage atomicity and logout wipe paths, OIDC PKCE flow, widget capability policy, thread notification serialization, quiet-hours math, Server ACL self-lockout guards, export pagination boundaries, PiP drag cleanup, and all Tauri hooks on the web build.

CI pipeline review (added after the code audit)

  • #90 CI: add a Playwright smoke test that boots the built client and exercises the composer in an E2EE room high
  • #91 CI: npm audit is informational — high-severity advisories should fail pull requests medium
  • #92 CI: built output is never loaded — add a vite preview boot check after npm run build medium
  • #93 CI: the Docker image is never built — add a docker build job so the documented deploy path can't rot medium
  • #94 CI: no dependency update automation (Renovate/Dependabot) medium
  • #95 CI: no secret scanning, and config.json (incl. any gifApiKey) ships inside the build image medium
  • #96 CI: bundle size is reported but has no budget — regressions pass green low
  • #97 CI: ESLint runs without --max-warnings, so new react-hooks/exhaustive-deps warnings never surface low
  • #98 CI: the lotus-build.sh upstream-merge path deploys without running CI high
  • #99 Repo hygiene: eight dead GitHub Actions workflows under .github/workflows never run on Gitea low
  • #100 Process: no CODEOWNERS or required review — a single push to lotus deploys to production in ~11 minutes low

Element Call fork

The embedded call widget was audited separately; 34 issues are tracked in LotusGuild/element-call#35.

# September 2026 client audit — tracking issue Eight parallel code-reading passes over the `lotus` branch (calls, messaging/threads, security/persistence, settings/appearance, notifications/desktop, navigation/moderation, accessibility/mobile, tooling) at commit 4bea4895. Every finding below was verified against the code before filing; items already open in `LOTUS_TODO.md` were deliberately not refiled. **Labelling scheme:** each issue carries one `priority: *`, one type label (`bug` / `security` / `performance` / `a11y` / `ux` / `tech-debt` / `dependencies` / `docs`), and one `area: *` label. Milestones group them into **P0 security & data loss** (due 2026-09-26), **High** (due 2026-10-15) and **Medium & Low** (due 2026-11-30); critical issues also carry an individual due date of 2026-09-19. **Totals:** 83 issues — 2 critical, 17 high, 31 medium, 33 low. **By area:** messaging 18, calls 18, settings 8, appearance 7, build-ci 6, auth-session 5, navigation 5, threads 4, notifications 4, moderation 3, desktop 3, media 2. ## Critical - [ ] #6 Image compression in an E2EE room uploads plaintext and breaks the event `security` `messaging` - [ ] #7 Scheduled messages bypass encryption — plaintext `m.room.message` in E2EE rooms `security` `messaging` ## High - [ ] #8 Answering an incoming video call turns the camera on, ignoring the user setting `bug` `calls` - [ ] #9 Push-to-talk, the deafen hotkey and AFK auto-mute stop working outside the call room `bug` `calls` - [ ] #10 Bookmarks and reminders upload decrypted message text to server account data `security` `messaging` - [ ] #11 GIF picker sends unencrypted media into encrypted rooms `security` `messaging` - [ ] #12 Edits and reactions on a thread root are invisible in the thread panel `bug` `threads` - [ ] #13 Search date-range filter is silently ignored for unencrypted rooms `bug` `messaging` - [ ] #14 Persistent search index keeps decrypted plaintext of redacted messages and left rooms `security` `messaging` - [ ] #15 Room widgets never render — matrix-widget-api 1.17.0 `isValidUrl` is broken `bug` `messaging` - [ ] #16 OIDC token refresh force-reloads every other tab, dropping in-progress calls `bug` `auth-session` - [ ] #17 Renaming two rooms in quick succession silently loses the first rename `bug` `settings` - [ ] #18 Private user notes can lose typed text (store echo reverts draft; close within debounce drops save) `bug` `settings` - [ ] #19 Advanced Push Rule Editor silently strips the `highlight` tweak from mention rules `bug` `notifications` - [ ] #20 Favouriting/low-priority a room doesn't move it in the sidebar until an unrelated re-render `bug` `navigation` - [ ] #21 Timed room mute silently overwrites a manual notification-mode change made during the mute window `bug` `navigation` - [ ] #22 Policy List Viewer misses legacy Draupnir/Mjolnir policy event types, showing false "no rules found" `bug` `moderation` - [ ] #23 Call hotkeys: rebind has no denylist, modifiers ignored, Space swallowed on buttons, duplicate bindings allowed `a11y` `calls` - [ ] #24 npm audit: 11 advisories in runtime deps (pdfjs-dist JS-execution, react-router, sanitize-html, katex, uuid) `dependencies` `build-ci` ## Medium - [ ] #25 Right-clicking the call button silently starts a camera-on video call `ux` `calls` - [ ] #26 Call-status bar bypasses the room screenshare/camera policy and the share confirmation `bug` `calls` - [ ] #27 PTT window blur/focus force-mutes unconditionally, revoking a manual unmute `bug` `calls` - [ ] #28 Incoming-call ringtone ignores quiet hours, DND and Focus Assist `ux` `calls` - [ ] #29 Logging out mid-call never hangs up, leaving a ghost RTC membership `bug` `calls` - [ ] #30 Voice-channel "Channel Full" limit is bypassed by joining from the sidebar `bug` `calls` - [ ] #31 Soundboard upload limit double-counts staged clips and reads a stale count `bug` `calls` - [ ] #32 Speaker set is reallocated on every call-state push, re-rendering the app-wide call bar `performance` `calls` - [ ] #33 Deafen button's aria-label is inverted relative to its tooltip `a11y` `calls` - [ ] #34 Thread read receipts ignore the "Hide Typing & Read Receipts" setting `security` `threads` - [ ] #35 Creating a poll from the thread composer posts it to the main timeline `bug` `threads` - [ ] #36 Scheduling from the composer drops formatting, mentions and the reply relation `bug` `messaging` - [ ] #37 Composer drafts are lost on page reload (only written on unmount) `bug` `messaging` - [ ] #38 Thread panel never shows read receipts (context provider is out of scope) `bug` `threads` - [ ] #39 Every text message subscribes to one global translation atom → timeline-wide re-render storms `performance` `messaging` - [ ] #40 Read-position recompute walks all joined members and re-renders the whole timeline `performance` `messaging` - [ ] #41 Composer drafts survive logout and are auto-hydrated into the next account's session `security` `auth-session` - [ ] #42 Settings are never synced across tabs — one tab's whole blob clobbers the other's `bug` `settings` - [ ] #43 Element Call iframe is same-origin, so its `sandbox` is a no-op `security` `calls` - [ ] #44 Shipped nginx/Netlify configs set no CSP, frame-ancestors or referrer policy `security` `build-ci` - [ ] #45 Logout's search-index wipe can report success while another tab still holds the DB `security` `auth-session` - [ ] #46 Avatar Decoration settings panel shows "None" on any fetch failure, not just true absence `bug` `settings` - [ ] #47 Profile field edits can be clobbered by a slow-loading initial fetch (pronouns, timezone, avatar decoration) `bug` `settings` - [ ] #48 Notification profile presets don't clear an active "Pause Notifications" snooze `bug` `notifications` - [ ] #49 "Deny" on a pending knock request has no error handling and can be shown to users who lack kick power `bug` `moderation` - [ ] #50 Room-history export shows outdated content and a garbled duplicate line for edited messages `bug` `navigation` - [ ] #51 Room invite link / QR code always uses the local homeserver as the only via-server, ignoring the shared via-server resolver `bug` `navigation` - [ ] #52 Primary accent color fails WCAG AA 4.5:1 as text in Midnight and Lotus Terminal Light themes `a11y` `appearance` - [ ] #53 Lotus-added UI bypasses the app's i18next mechanism entirely — always renders in English regardless of user locale `ux` `settings` - [ ] #54 Package.json engines version misalignment with README and CI `tech-debt` `build-ci` - [ ] #55 Fragile postinstall patch script for folds Icon `tech-debt` `build-ci` ## Low - [ ] #56 "Focus camera" pins a participant with no way to unpin `ux` `calls` - [ ] #57 Soundboard clip blob URLs are never revoked and survive logout `security` `calls` - [ ] #58 No warning when closing the tab or reloading during an active call `ux` `calls` - [ ] #59 `CallControl.setPipMode` writes state nothing reads `tech-debt` `calls` - [ ] #60 In-iframe PTT/deafen key listeners are lost if the EC iframe reloads `bug` `calls` - [ ] #61 `RoomMemberEvent.Membership` listener on `Room` never fires (dead handler) `bug` `messaging` - [ ] #62 Sender-only search returns membership/state/reaction events as results `ux` `messaging` - [ ] #63 Forwarding an encrypted attachment into a plaintext room publishes its AES key `security` `messaging` - [ ] #64 Avatar-decoration slug from a remote profile is interpolated into a URL unvalidated `security` `media` - [ ] #65 KaTeX renders remote LaTeX with no `maxSize`/`maxExpand` limits `security` `messaging` - [ ] #66 Location messages load an OpenStreetMap iframe with no facade or auto-load gate `security` `messaging` - [ ] #67 Any OIDC login failure discards the cached dynamic client id `bug` `auth-session` - [ ] #68 GIF picker sends search terms and IP directly to Giphy, outside any privacy setting `security` `messaging` - [ ] #69 Cross-tab session sync is absent on the auth pages `bug` `auth-session` - [ ] #70 Documented "leading emoji rendered 1.15× larger" room-name feature is not implemented `docs` `appearance` - [ ] #71 Documented secondary glow/blink keyframes for animated backgrounds don't exist `docs` `appearance` - [ ] #72 Earth Day globe decoration risks sitting over the composer `ux` `appearance` - [ ] #73 Halloween fog-band layers keep `willChange` set even when motion is fully disabled `performance` `appearance` - [ ] #74 `seasonalThemeOverride` is the only enum setting with no persisted-value validation `tech-debt` `settings` - [ ] #75 Page Zoom input silently discards a typed-but-unconfirmed value on blur `ux` `settings` - [ ] #76 `useTauriCallPower` never releases the native keep-awake state on unmount `bug` `desktop` - [ ] #77 `getNotificationType`/main-timeline gate re-derives push rules on every event without memoization `performance` `notifications` - [ ] #78 `notificationclick` always focuses the first matching window client, not the one most relevant to the notification `ux` `desktop` - [ ] #79 Quick Reply's failure is swallowed with no user-visible feedback `ux` `desktop` - [ ] #80 `LotusToastContainer` toast auto-dismiss timer is not reset in a fixed slot, so a fast burst can hide a toast before it's read `ux` `notifications` - [ ] #81 Room-history export gives no indication that a message was media/an attachment `bug` `media` - [ ] #82 Room Activity Log can misclassify membership transitions for back-paginated (older) events `bug` `moderation` - [ ] #83 Room Insights ("stats") panel freezes at open-time data and never reflects new activity `ux` `navigation` - [ ] #84 `SendingSpinClass` and `CallAvatarAnimation` ignore `prefers-reduced-motion` `a11y` `appearance` - [ ] #85 `CollapsibleBody`'s reduced-motion check is a one-time snapshot, not a live listener `a11y` `messaging` - [ ] #86 Terminal boot sequence has no touch/click dismiss and isn't hidden from assistive tech `a11y` `appearance` - [ ] #87 Dead code: unused useForceUpdate.js `tech-debt` `build-ci` - [ ] #88 syncDecorations.mjs fragile regex parsing `tech-debt` `build-ci` ## Fix-first recommendation 1. #6 and #7 (E2EE plaintext leaks in the composer) — small, contained fixes; ship together. 2. #10, #11, #14 (plaintext leaving the device via account data, unencrypted GIF media, and the redaction gap in the persistent search index). 3. #8 and #9 (camera on answer; PTT/AFK dead in picture-in-picture) — the two highest-impact call bugs. 4. #16 (OIDC refresh reloading other tabs) before any multi-tab call workflows are relied on. 5. #24 dependency bumps, with pdfjs-dist first. ## Things checked and found sound Sanitizer configuration (the sanitize-html advisories do not apply to this usage), KaTeX trust settings, all `window.open` / `target=_blank` sites, service-worker media auth scoping, session storage atomicity and logout wipe paths, OIDC PKCE flow, widget capability policy, thread notification serialization, quiet-hours math, Server ACL self-lockout guards, export pagination boundaries, PiP drag cleanup, and all Tauri hooks on the web build. ## CI pipeline review (added after the code audit) - [ ] #90 CI: add a Playwright smoke test that boots the built client and exercises the composer in an E2EE room `high` - [ ] #91 CI: `npm audit` is informational — high-severity advisories should fail pull requests `medium` - [ ] #92 CI: built output is never loaded — add a `vite preview` boot check after `npm run build` `medium` - [ ] #93 CI: the Docker image is never built — add a `docker build` job so the documented deploy path can't rot `medium` - [ ] #94 CI: no dependency update automation (Renovate/Dependabot) `medium` - [ ] #95 CI: no secret scanning, and `config.json` (incl. any `gifApiKey`) ships inside the build image `medium` - [ ] #96 CI: bundle size is reported but has no budget — regressions pass green `low` - [ ] #97 CI: ESLint runs without `--max-warnings`, so new `react-hooks/exhaustive-deps` warnings never surface `low` - [ ] #98 CI: the `lotus-build.sh` upstream-merge path deploys without running CI `high` - [ ] #99 Repo hygiene: eight dead GitHub Actions workflows under `.github/workflows` never run on Gitea `low` - [ ] #100 Process: no CODEOWNERS or required review — a single push to `lotus` deploys to production in ~11 minutes `low` ## Element Call fork The embedded call widget was audited separately; 34 issues are tracked in LotusGuild/element-call#35.
jared self-assigned this 2026-09-12 01:52:08 -04:00
jared pinned this 2026-09-12 01:52:08 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#89