EC fork audit 2026-09 — tracking index #35

Closed
opened 2026-09-12 02:13:45 -04:00 by jared · 0 comments
Owner

Element Call fork audit 2026-09 — tracking index

Four parallel code-reading passes over lotus @ e36aef8a (audio pipeline, widget action bus & security, in-call UI/mobile, build/publish/upstream-sync), scoped to git diff v0.20.1..lotus. Every finding was verified against the code before filing; the P6-2 items already open in cinny's LOTUS_TODO.md were not refiled. Host-side halves of cross-cutting problems are filed in LotusGuild/cinny and cross-referenced from the relevant issue here.

Labelling: one priority: *, one type label, one area: * per issue; milestones High (due 2026-10-15) and Medium & Low (due 2026-11-30).

Totals: 34 issues — 6 high, 17 medium, 11 low.

By area: denoise 8, widget-actions 8, decorations 4, build-publish 4, mobile-ui 3, livekit 2, soundboard-inject 2, deafen 1, upstream-sync 1, audio-pipeline 1.

High

  • #1 Deafen via per-participant setVolume is undone by EC volume controls and clobbers tile volumes on undeafen bug deafen
  • #2 Denoise restart fallback hands LiveKit the raw mic as processedTrack, which later gets stopped bug denoise
  • #3 focus_participant is a silent no-op in EC's default grid layout bug widget-actions
  • #4 In-call avatar decoration is sized 1:1 with the avatar (no inset like the host) and has no onError fallback bug decorations
  • #5 Fork CI never runs the test/lint/typecheck suite before publish bug build-publish
  • #6 Nothing in CI or the test suite exercises src/lotus/* at all tech-debt build-publish

Medium

  • #7 Denoise init blocks LiveKit's track-change lock, freezing mute/unmute for seconds ux denoise
  • #8 Model load failure silently publishes raw audio while the host UI says denoise is on bug denoise
  • #9 Denoise graph keeps running (full ML inference) while the mic is muted performance denoise
  • #10 Racing LocalTrackPublished events start two denoise processors (two AudioContexts, two wasm inits) performance denoise
  • #11 Sending null does not clear a quality cap, contrary to the host contract bug livekit
  • #12 Screenshare bitrate cap is applied per simulcast layer, so the aggregate can be N× the cap bug livekit
  • #13 Soundboard clips are published to peers even while the local mic is muted ux soundboard-inject
  • #14 Each soundboard clip creates a new AudioContext, against a per-document limit of ~6 performance soundboard-inject
  • #15 Incoming io.lotus.* toWidget actions are accepted from any origin security widget-actions
  • #16 Spotlight pin can never be cleared once set ux widget-actions
  • #17 Decoration roster and spotlight pin are lost on an EC handler remount and never re-pushed bug decorations
  • #18 Torn-down lotus actions pile up in the LazyEventEmitter backlog and replay stale bug widget-actions
  • #19 In-call avatar decoration (animated APNG) ignores prefers-reduced-motion, unlike the host renderer a11y decorations
  • #20 call_state can emit up to 8 messages/second; dedupe runs after the throttle performance widget-actions
  • #21 lotusTransparent/lotusTheme provide no contrast guard for an arbitrary host background ux mobile-ui
  • #22 Published npm version consumed by cinny has no corresponding git tag bug build-publish
  • #23 Upstream fork is 6 minor releases behind with no cherry-picks; hotspot files have heavy upstream churn tech-debt upstream-sync

Low

  • #24 Denoise worklet node ignores its channelCount: 1 (no channelCountMode: "explicit") bug denoise
  • #25 Dry/wet floor sums an undelayed dry copy with the delayed denoised path (comb filtering) bug denoise
  • #26 Denoise external-module contracts are untyped any and break silently on an asset bump tech-debt denoise
  • #27 Audio-output sink is never restored when the selected device becomes undefined again bug audio-pipeline
  • #28 Decoration URLs derive from an unvalidated arbitrary profile field security decorations
  • #29 Pinning during a screenshare removes the PiP even when the pin is a third party ux widget-actions
  • #30 Manual pin resolves by userId only, so multi-device users pin an arbitrary device bug widget-actions
  • #31 lotusCallState=1 in standalone (non-widget) mode builds the whole pipeline for nothing performance widget-actions
  • #32 Landscape filmstrip rail-shrink query is height-only, not orientation- or device-aware ux mobile-ui
  • #33 lotusFlag/lotusParam URL cache is captured once at module load, not re-read tech-debt mobile-ui
  • #34 CI workflow has no concurrency guard, unlike the cinny workflow it's modeled on performance build-publish

Fix-first recommendation

  1. #1 — reimplement deafen as EC's global output mute (setAudioEnabled$) instead of per-participant setVolume; this single change also fixes the first-joiner race and the known soundboard-audio divergence.
  2. #2 — one-line fix on the denoise fallback path (processedTrack = undefined), prevents a permanently dead mic.
  3. #5 + #6 — wire pnpm lint and pnpm test:unit into CI and add the first tests under src/lotus/, so everything else here has regression protection.
  4. #3 + #16 — make focus_participant force spotlight mode and clear the pin on leave/hangup (pair with cinny#56 for the unpin UI).
  5. #15strictOriginCheck = true on the widget transport; one line, closes the inbound-action origin gap.
  6. #23 — schedule the upstream rebase before the conflict surface grows further.

Checked and found sound

The 98fbdbd5 inject double-publish fix under every interleaving; denoise survival across LiveKit reconnects on the success path; safeMediaUrl/safeAssetBase scheme and origin checks; all lotus module teardowns; every toWidget handler replies before gating; decoration roster capping and ref-counting; upstream capability negotiation unchanged; pnpm audit --prod clean; no Lotus credentials in Dockerfile/config; knip/tsc/vitest globs include src/lotus/*.

Status 2026-09-13

All 34 issues closed. Shipped as 0.25.0-lotus.1 (upstream v0.25.0 merge + first fix round) and 0.25.0-lotus.2 (remaining 24), both CI-published; cinny pinned to 0.25.0-lotus.2 at ba0cc3e4. Host-side halves (request_state, focus by device id, soundboard muted reason) landed in cinny f50f50be.

# Element Call fork audit 2026-09 — tracking index Four parallel code-reading passes over `lotus` @ e36aef8a (audio pipeline, widget action bus & security, in-call UI/mobile, build/publish/upstream-sync), scoped to `git diff v0.20.1..lotus`. Every finding was verified against the code before filing; the P6-2 items already open in cinny's `LOTUS_TODO.md` were not refiled. Host-side halves of cross-cutting problems are filed in LotusGuild/cinny and cross-referenced from the relevant issue here. **Labelling:** one `priority: *`, one type label, one `area: *` per issue; milestones **High** (due 2026-10-15) and **Medium & Low** (due 2026-11-30). **Totals:** 34 issues — 6 high, 17 medium, 11 low. **By area:** denoise 8, widget-actions 8, decorations 4, build-publish 4, mobile-ui 3, livekit 2, soundboard-inject 2, deafen 1, upstream-sync 1, audio-pipeline 1. ## High - [ ] #1 Deafen via per-participant setVolume is undone by EC volume controls and clobbers tile volumes on undeafen `bug` `deafen` - [ ] #2 Denoise restart fallback hands LiveKit the raw mic as `processedTrack`, which later gets stopped `bug` `denoise` - [ ] #3 `focus_participant` is a silent no-op in EC's default grid layout `bug` `widget-actions` - [ ] #4 In-call avatar decoration is sized 1:1 with the avatar (no inset like the host) and has no onError fallback `bug` `decorations` - [ ] #5 Fork CI never runs the test/lint/typecheck suite before publish `bug` `build-publish` - [ ] #6 Nothing in CI or the test suite exercises `src/lotus/*` at all `tech-debt` `build-publish` ## Medium - [ ] #7 Denoise init blocks LiveKit's track-change lock, freezing mute/unmute for seconds `ux` `denoise` - [ ] #8 Model load failure silently publishes raw audio while the host UI says denoise is on `bug` `denoise` - [ ] #9 Denoise graph keeps running (full ML inference) while the mic is muted `performance` `denoise` - [ ] #10 Racing `LocalTrackPublished` events start two denoise processors (two AudioContexts, two wasm inits) `performance` `denoise` - [ ] #11 Sending `null` does not clear a quality cap, contrary to the host contract `bug` `livekit` - [ ] #12 Screenshare bitrate cap is applied per simulcast layer, so the aggregate can be N× the cap `bug` `livekit` - [ ] #13 Soundboard clips are published to peers even while the local mic is muted `ux` `soundboard-inject` - [ ] #14 Each soundboard clip creates a new AudioContext, against a per-document limit of ~6 `performance` `soundboard-inject` - [ ] #15 Incoming `io.lotus.*` toWidget actions are accepted from any origin `security` `widget-actions` - [ ] #16 Spotlight pin can never be cleared once set `ux` `widget-actions` - [ ] #17 Decoration roster and spotlight pin are lost on an EC handler remount and never re-pushed `bug` `decorations` - [ ] #18 Torn-down lotus actions pile up in the LazyEventEmitter backlog and replay stale `bug` `widget-actions` - [ ] #19 In-call avatar decoration (animated APNG) ignores prefers-reduced-motion, unlike the host renderer `a11y` `decorations` - [ ] #20 `call_state` can emit up to 8 messages/second; dedupe runs after the throttle `performance` `widget-actions` - [ ] #21 `lotusTransparent`/`lotusTheme` provide no contrast guard for an arbitrary host background `ux` `mobile-ui` - [ ] #22 Published npm version consumed by cinny has no corresponding git tag `bug` `build-publish` - [ ] #23 Upstream fork is 6 minor releases behind with no cherry-picks; hotspot files have heavy upstream churn `tech-debt` `upstream-sync` ## Low - [ ] #24 Denoise worklet node ignores its `channelCount: 1` (no `channelCountMode: "explicit"`) `bug` `denoise` - [ ] #25 Dry/wet floor sums an undelayed dry copy with the delayed denoised path (comb filtering) `bug` `denoise` - [ ] #26 Denoise external-module contracts are untyped `any` and break silently on an asset bump `tech-debt` `denoise` - [ ] #27 Audio-output sink is never restored when the selected device becomes undefined again `bug` `audio-pipeline` - [ ] #28 Decoration URLs derive from an unvalidated arbitrary profile field `security` `decorations` - [ ] #29 Pinning during a screenshare removes the PiP even when the pin is a third party `ux` `widget-actions` - [ ] #30 Manual pin resolves by userId only, so multi-device users pin an arbitrary device `bug` `widget-actions` - [ ] #31 `lotusCallState=1` in standalone (non-widget) mode builds the whole pipeline for nothing `performance` `widget-actions` - [ ] #32 Landscape filmstrip rail-shrink query is height-only, not orientation- or device-aware `ux` `mobile-ui` - [ ] #33 `lotusFlag`/`lotusParam` URL cache is captured once at module load, not re-read `tech-debt` `mobile-ui` - [ ] #34 CI workflow has no `concurrency` guard, unlike the cinny workflow it's modeled on `performance` `build-publish` ## Fix-first recommendation 1. #1 — reimplement deafen as EC's global output mute (`setAudioEnabled$`) instead of per-participant `setVolume`; this single change also fixes the first-joiner race and the known soundboard-audio divergence. 2. #2 — one-line fix on the denoise fallback path (`processedTrack = undefined`), prevents a permanently dead mic. 3. #5 + #6 — wire `pnpm lint` and `pnpm test:unit` into CI and add the first tests under `src/lotus/`, so everything else here has regression protection. 4. #3 + #16 — make `focus_participant` force spotlight mode and clear the pin on leave/hangup (pair with cinny#56 for the unpin UI). 5. #15 — `strictOriginCheck = true` on the widget transport; one line, closes the inbound-action origin gap. 6. #23 — schedule the upstream rebase before the conflict surface grows further. ## Checked and found sound The `98fbdbd5` inject double-publish fix under every interleaving; denoise survival across LiveKit reconnects on the success path; `safeMediaUrl`/`safeAssetBase` scheme and origin checks; all lotus module teardowns; every toWidget handler replies before gating; decoration roster capping and ref-counting; upstream capability negotiation unchanged; `pnpm audit --prod` clean; no Lotus credentials in Dockerfile/config; knip/tsc/vitest globs include `src/lotus/*`. ## Status 2026-09-13 All 34 issues closed. Shipped as `0.25.0-lotus.1` (upstream v0.25.0 merge + first fix round) and `0.25.0-lotus.2` (remaining 24), both CI-published; cinny pinned to `0.25.0-lotus.2` at ba0cc3e4. Host-side halves (request_state, focus by device id, soundboard muted reason) landed in cinny f50f50be.
jared self-assigned this 2026-09-12 02:13:45 -04:00
jared pinned this 2026-09-12 02:13:45 -04:00
jared closed this issue 2026-09-13 01:28:40 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/element-call#35