Desktop half of cinny #43. Pairs with LotusGuild/cinny#252. Stacked on #26 (WebView permissions): this PR's base is that branch.
The bundled Element Call page ran on the app's own origin, so the call frame could read the app's storage (login token) and DOM. It's now served from http://127.0.0.1:44548: the same local server and bundle, but a separate origin.
Changes
Server bind: the local server binds 127.0.0.1 explicitly. The app is still loaded as http://localhost:44548, so its storage and login are untouched. Binding the namelocalhost can pick ::1 only (Windows lists it first), and then 127.0.0.1 wouldn't answer.
Opt-in:config.json gets desktopCallOrigin: "http://127.0.0.1:44548". cinny uses it only when set (cinny#252).
CSP:frame-src allows http://127.0.0.1:44548.
Permissions: the call page's origin gets microphone/camera/screen only; notifications, location and device info are denied (unit-tested).
No IPC: the call page gets none, because the capability matches only http://localhost:44548.
Merge order
Either order is safe:
this PR without cinny#252: the key is ignored, and the bind and CSP are harmless;
cinny#252 without this PR: inert.
Merge #26 first, then this PR (retarget it to main after #26 merges).
Tested
Linux release build:
the server listens on 127.0.0.1:44548, and the app loads as http://localhost:44548;
the call page loads from 127.0.0.1 inside the app, under its CSP;
from that frame, parent.localStorage and parent.document give SecurityError, while a same-origin frame (the old setup) reads the app's stored secret.
Calls: Linux WebKitGTK has no WebRTC, so the call was tested in a simulated desktop in Chromium (the WebView2 engine). Join, speaking, mic, screenshare, layout and hang-up all pass; details are in cinny#252.
Rust: 17 tests pass; the Windows code type-checks.
⚠️ Before merging: test on Windows
This is where desktop calls actually work, and I can't run WebView2 here. Please check:
The app starts and you're still logged in. Same origin, so the login should survive. This is the first thing that would break if the Windows engine didn't fall back from ::1 to 127.0.0.1.
A call: mic, camera, screenshare, soundboard, push-to-talk.
Isolation: in devtools on the call frame, parent.localStorage should throw.
Desktop half of **cinny #43**. Pairs with **LotusGuild/cinny#252**. **Stacked on #26** (WebView permissions): this PR's base is that branch.
The bundled Element Call page ran on the app's own origin, so the call frame could read the app's storage (login token) and DOM. It's now served from **`http://127.0.0.1:44548`**: the same local server and bundle, but a separate origin.
## Changes
- **Server bind:** the local server binds **`127.0.0.1`** explicitly. The app is still loaded as `http://localhost:44548`, so its storage and login are untouched. Binding the *name* `localhost` can pick `::1` only (Windows lists it first), and then 127.0.0.1 wouldn't answer.
- **Opt-in:** `config.json` gets `desktopCallOrigin: "http://127.0.0.1:44548"`. cinny uses it only when set (cinny#252).
- **CSP:** `frame-src` allows `http://127.0.0.1:44548`.
- **Permissions:** the call page's origin gets **microphone/camera/screen only**; notifications, location and device info are denied (unit-tested).
- **No IPC:** the call page gets none, because the capability matches only `http://localhost:44548`.
## Merge order
Either order is safe:
- this PR without cinny#252: the key is ignored, and the bind and CSP are harmless;
- cinny#252 without this PR: inert.
**Merge #26 first**, then this PR (retarget it to `main` after #26 merges).
## Tested
- **Linux release build:**
- the server listens on `127.0.0.1:44548`, and the app loads as `http://localhost:44548`;
- the call page loads from 127.0.0.1 inside the app, under its CSP;
- from that frame, `parent.localStorage` and `parent.document` give **SecurityError**, while a same-origin frame (the old setup) **reads the app's stored secret**.
- **Calls:** Linux WebKitGTK has no WebRTC, so the call was tested in a simulated desktop in Chromium (the WebView2 engine). Join, speaking, mic, screenshare, layout and hang-up all pass; details are in cinny#252.
- **Rust:** 17 tests pass; the Windows code type-checks.
## ⚠️ Before merging: test on Windows
This is where desktop calls actually work, and I can't run WebView2 here. Please check:
1. **The app starts and you're still logged in.** Same origin, so the login should survive. This is the first thing that would break if the Windows engine didn't fall back from `::1` to 127.0.0.1.
2. **A call:** mic, camera, screenshare, soundboard, push-to-talk.
3. **Isolation:** in devtools on the call frame, `parent.localStorage` should throw.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The bundled Element Call page ran on the app's own origin
(http://localhost:44548), so the call frame could read the app's storage
(login token) and DOM. Serve it from http://127.0.0.1:44548 instead: the
same local server and bundle, a different origin.
- The local server binds 127.0.0.1 explicitly. The app is still loaded as
http://localhost:44548 (its storage stays where it is; the engines try
127.0.0.1 for `localhost`). Binding the name `localhost` could pick ::1
only (Windows lists it first), and then 127.0.0.1 wouldn't answer.
- config.json: desktopCallOrigin = http://127.0.0.1:44548. cinny loads the
call page from there only when this is set (cinny #43 PR).
- CSP frame-src allows http://127.0.0.1:44548.
- Permissions (on top of #22): the call page's origin gets microphone/
camera/screen only; nothing else.
- The call page gets no IPC: the capability only matches
http://localhost:44548.
Tested (Linux release build): the server listens on 127.0.0.1:44548 and
the app loads as http://localhost:44548; the call page loads from
127.0.0.1 inside the app under its CSP; from that frame parent.localStorage
and parent.document are SecurityError, while a same-origin frame (the old
setup) reads the app's storage. The call itself was tested in a simulated
desktop (Chromium, the WebView2 engine) against a local Synapse + LiveKit;
see the cinny PR. Rust tests 17 passed; Windows code type-checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
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.
Desktop half of cinny #43. Pairs with LotusGuild/cinny#252. Stacked on #26 (WebView permissions): this PR's base is that branch.
The bundled Element Call page ran on the app's own origin, so the call frame could read the app's storage (login token) and DOM. It's now served from
http://127.0.0.1:44548: the same local server and bundle, but a separate origin.Changes
127.0.0.1explicitly. The app is still loaded ashttp://localhost:44548, so its storage and login are untouched. Binding the namelocalhostcan pick::1only (Windows lists it first), and then 127.0.0.1 wouldn't answer.config.jsongetsdesktopCallOrigin: "http://127.0.0.1:44548". cinny uses it only when set (cinny#252).frame-srcallowshttp://127.0.0.1:44548.http://localhost:44548.Merge order
Either order is safe:
Merge #26 first, then this PR (retarget it to
mainafter #26 merges).Tested
127.0.0.1:44548, and the app loads ashttp://localhost:44548;parent.localStorageandparent.documentgive SecurityError, while a same-origin frame (the old setup) reads the app's stored secret.⚠️ Before merging: test on Windows
This is where desktop calls actually work, and I can't run WebView2 here. Please check:
::1to 127.0.0.1.parent.localStorageshould throw.🤖 Generated with Claude Code
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA