Incoming io.lotus.* toWidget actions are accepted from any origin #15

Closed
opened 2026-09-12 02:13:04 -04:00 by jared · 1 comment
Owner

Severity: medium · Type: security · Confidence: high

Location: src/widget.ts:104-113, src/lotus/lotusActions.ts:35-41, node_modules/.pnpm/matrix-widget-api@1.17.0/.../transport/PostmessageTransport.js:55,66

Problem

PostmessageTransport.handleMessage skips origin validation unless strictOriginCheck is set (default false, and EC never sets it); it also does not check ev.source or that widgetId matches. Outbound sends are pinned to clientOrigin, but inbound requests from any frame that can postMessage the EC window are dispatched. The fork widens the blast radius of this inherited weakness by registering five new toWidget actions in initializeWidget that mutate live call behaviour: inject_audio (publishes an attacker-supplied audio URL as a real LiveKit track into the call), set_deafen, set_quality, focus_participant, and decorations (renders attacker-chosen https images on tiles). Upstream only exposed hangup/device_mute here.

How to trigger

any page holding a window handle on the EC iframe/window (opener, embedder, or a same-origin frame) posts {api:"toWidget", action:"io.lotus.inject_audio", widgetId:<any>, requestId:"x", data:{url:"https://evil/clip.mp3"}}.

Suggested fix

after constructing the WidgetApi, set api.transport.strictOriginCheck = true — in the Lotus deployment cinny serves EC from its own origin (/public/element-call/index.html), so globalThis.origin === parentOrigin and the check passes; otherwise add an explicit ev.origin === parentOrigin guard before dispatching lotus actions.


Filed from the September 2026 audit (branch lotus).

**Severity:** medium · **Type:** security · **Confidence:** high **Location:** `src/widget.ts:104-113`, `src/lotus/lotusActions.ts:35-41`, `node_modules/.pnpm/matrix-widget-api@1.17.0/.../transport/PostmessageTransport.js:55,66` ### Problem `PostmessageTransport.handleMessage` skips origin validation unless `strictOriginCheck` is set (default `false`, and EC never sets it); it also does not check `ev.source` or that `widgetId` matches. Outbound sends are pinned to `clientOrigin`, but *inbound* requests from any frame that can `postMessage` the EC window are dispatched. The fork widens the blast radius of this inherited weakness by registering five new toWidget actions in `initializeWidget` that mutate live call behaviour: `inject_audio` (publishes an attacker-supplied audio URL as a real LiveKit track into the call), `set_deafen`, `set_quality`, `focus_participant`, and `decorations` (renders attacker-chosen https images on tiles). Upstream only exposed hangup/device_mute here. ### How to trigger any page holding a window handle on the EC iframe/window (opener, embedder, or a same-origin frame) posts `{api:"toWidget", action:"io.lotus.inject_audio", widgetId:<any>, requestId:"x", data:{url:"https://evil/clip.mp3"}}`. ### Suggested fix after constructing the `WidgetApi`, set `api.transport.strictOriginCheck = true` — in the Lotus deployment cinny serves EC from its own origin (`/public/element-call/index.html`), so `globalThis.origin === parentOrigin` and the check passes; otherwise add an explicit `ev.origin === parentOrigin` guard before dispatching lotus actions. --- _Filed from the September 2026 audit (branch `lotus`)._
jared added this to the EC fork audit 2026-09 · Medium & Low milestone 2026-09-12 02:13:04 -04:00
jared added the bugpriority: mediumsecurityarea: widget-actions labels 2026-09-12 02:13:04 -04:00
jared self-assigned this 2026-09-12 02:13:04 -04:00
jared closed this issue 2026-09-12 11:56:46 -04:00
Author
Owner

Fixed in 37c9348e, shipped in 0.25.0-lotus.1 (published by CI from tag v0.25.0-lotus.1) and consumed by cinny at 9a85a487.

Fixed in 37c9348e, shipped in `0.25.0-lotus.1` (published by CI from tag v0.25.0-lotus.1) and consumed by cinny at 9a85a487.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
2026-11-14
Dependencies

No dependencies set.

Reference: LotusGuild/element-call#15