Call page on its own origin, http://127.0.0.1:44548 (cinny #43) #27

Merged
jared merged 2 commits from desktop-call-origin into main 2026-09-30 20:17:25 -04:00
Owner

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.ai/code/session_01PPmy3tPq869XDW4njjVaKA

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
jared changed target branch from webview-permission-origin to main 2026-09-30 20:17:19 -04:00
jared added 2 commits 2026-09-30 20:17:19 -04:00
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
# Conflicts:
#	config.json
jared merged commit 03e3937e21 into main 2026-09-30 20:17:25 -04:00
Sign in to join this conversation.