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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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,66Problem
PostmessageTransport.handleMessageskips origin validation unlessstrictOriginCheckis set (defaultfalse, and EC never sets it); it also does not checkev.sourceor thatwidgetIdmatches. Outbound sends are pinned toclientOrigin, but inbound requests from any frame that canpostMessagethe EC window are dispatched. The fork widens the blast radius of this inherited weakness by registering five new toWidget actions ininitializeWidgetthat 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, anddecorations(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, setapi.transport.strictOriginCheck = true— in the Lotus deployment cinny serves EC from its own origin (/public/element-call/index.html), soglobalThis.origin === parentOriginand the check passes; otherwise add an explicitev.origin === parentOriginguard before dispatching lotus actions.Filed from the September 2026 audit (branch
lotus).Fixed in
37c9348e, shipped in0.25.0-lotus.1(published by CI from tag v0.25.0-lotus.1) and consumed by cinny at 9a85a487.