WebView permissions only for the app's own origin (#22) #26

Merged
jared merged 2 commits from webview-permission-origin into main 2026-09-30 20:17:19 -04:00
Owner

Fixes #22: permission requests are granted only to the app's own origin.

Before

  • Linux (WebKitGTK): request.allow() for every request of every kind (mic, camera, location, clipboard, storage access, pointer lock, DRM, …), whatever page was loaded.
  • Windows (WebView2): mic, camera and notifications were auto-allowed with no check on who asked.

After

The policy lives in src-tauri/src/webview_permissions.rs and is unit-tested.

app (http://localhost:44548) any other origin other kinds
Linux allow mic/camera/screen, device labels, notifications, location deny deny (WebKitGTK has no prompt)
Windows allow mic/camera, notifications; location keeps WebView2's prompt deny mic/camera/notifications/location WebView2 default (unchanged)
  • Debug builds: also accept the bundled page (tauri://localhost, http://tauri.localhost) and devUrl, so tauri dev still works.
  • Frame checks: WebView2 reports the frame that asked (args.Uri()), so a widget or embed is refused directly. WebKitGTK doesn't say which frame asked, so the check there is on the page in the window. Frames are gated earlier by the Permissions Policy: cinny gives microphone; camera only to the call frame, which on desktop is always the same-origin bundled Element Call.
  • Logging: denials are logged (webview: denied … to <uri>).

Testing (Linux, release build, Xvfb + PulseAudio virtual mic)

A throwaway build served a probe page as the app (getUserMedia, enumerateDevices, Notification, geolocation). It added frames and then navigated to a foreign page. It ran without WebDriver, because WebKit's automation mode auto-grants media and bypasses the handler, which I checked.

probe old handler new handler
app page mic ✅ labels ✅ location allowed same
same-origin frame with the call frame's allow= mic ✅ same
cross-origin frame, no allow= mic ❌, location ❌ (blocked before the handler) same
cross-origin frame with allow="microphone" mic ✅ same (Linux limit above; cinny never sets this on foreign frames)
foreign top-level page mic ✅ labels ✅ location allowed mic ❌ labels hidden, location ❌ (logged)

(Location "allowed" means it got past permission, then failed with "no geolocation service" in the container.)

  • Real cinny build: boots, logs in against a local Synapse, navigator.mediaDevices is present, the notification shim reports granted, no denials logged. Clipboard write + read-back (the #156 recovery-key wipe) still works, though that was checked under WebDriver only.
  • Rust tests: cargo test --lib 16 passed. Windows code type-checked (cargo check --target x86_64-pc-windows-gnu, no new warnings). The Windows runtime path isn't exercised here: please try a call, a screenshare and a notification on the Windows build before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA

Fixes **#22**: permission requests are granted only to the app's own origin. ## Before - **Linux (WebKitGTK):** `request.allow()` for **every** request of every kind (mic, camera, location, clipboard, storage access, pointer lock, DRM, …), whatever page was loaded. - **Windows (WebView2):** mic, camera and notifications were auto-allowed with no check on who asked. ## After The policy lives in `src-tauri/src/webview_permissions.rs` and is unit-tested. | | app (`http://localhost:44548`) | any other origin | other kinds | |---|---|---|---| | **Linux** | allow mic/camera/screen, device labels, notifications, location | **deny** | **deny** (WebKitGTK has no prompt) | | **Windows** | allow mic/camera, notifications; location keeps WebView2's prompt | **deny** mic/camera/notifications/location | WebView2 default (unchanged) | - **Debug builds:** also accept the bundled page (`tauri://localhost`, `http://tauri.localhost`) and `devUrl`, so `tauri dev` still works. - **Frame checks:** WebView2 reports the frame that asked (`args.Uri()`), so a widget or embed is refused directly. WebKitGTK doesn't say which frame asked, so the check there is on the page in the window. Frames are gated earlier by the Permissions Policy: cinny gives `microphone; camera` only to the call frame, which on desktop is always the same-origin bundled Element Call. - **Logging:** denials are logged (`webview: denied … to <uri>`). ## Testing (Linux, release build, Xvfb + PulseAudio virtual mic) A throwaway build served a probe page as the app (getUserMedia, enumerateDevices, Notification, geolocation). It added frames and then navigated to a foreign page. It ran **without WebDriver**, because WebKit's automation mode auto-grants media and bypasses the handler, which I checked. | probe | old handler | new handler | |---|---|---| | app page | mic ✅ labels ✅ location allowed | same | | same-origin frame with the call frame's `allow=` | mic ✅ | same | | cross-origin frame, no `allow=` | mic ❌, location ❌ (blocked before the handler) | same | | cross-origin frame with `allow="microphone"` | mic ✅ | same (Linux limit above; cinny never sets this on foreign frames) | | **foreign top-level page** | mic ✅ labels ✅ location allowed | **mic ❌ labels hidden, location ❌ (logged)** | (Location "allowed" means it got past permission, then failed with "no geolocation service" in the container.) - **Real cinny build:** boots, logs in against a local Synapse, `navigator.mediaDevices` is present, the notification shim reports `granted`, no denials logged. Clipboard write + read-back (the #156 recovery-key wipe) still works, though that was checked under WebDriver only. - **Rust tests:** `cargo test --lib` 16 passed. Windows code type-checked (`cargo check --target x86_64-pc-windows-gnu`, no new warnings). The Windows runtime path isn't exercised here: **please try a call, a screenshare and a notification on the Windows build before merging.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
jared added 1 commit 2026-09-28 20:03:32 -04:00
Linux (WebKitGTK) allowed every permission request of every kind; Windows
(WebView2) auto-allowed mic/camera/notifications without checking who asked.

Now (src-tauri/src/webview_permissions.rs, unit-tested):
- Linux: microphone/camera/screen, device labels, notifications and location
  are granted when the page in the window is the app
  (http://localhost:44548; debug builds also the bundled/dev page).
  Everything else is denied (WebKitGTK has no prompt of its own). WebKitGTK
  doesn't say which frame asked; frames are gated earlier by the Permissions
  Policy (cinny gives microphone/camera only to the same-origin call frame).
- Windows: the same grants (minus location, which keeps WebView2's prompt),
  checked against the origin of the frame that asked (args.Uri()). Other
  origins are denied mic/camera/notifications/location; other kinds keep
  WebView2's default handling.
- Denials are logged ("webview: denied …").

Tested on Linux with a release build under Xvfb + PulseAudio (no WebDriver:
WebKit's automation mode bypasses the handler), before/after:
- app page: mic, device labels, location allowed (unchanged)
- same-origin call frame: mic allowed (unchanged)
- cross-origin frame without allow=: blocked before the handler (unchanged)
- foreign top-level page: mic, device labels, location now denied (were
  allowed)
Real cinny build: boots, logs in, no denials. Windows code type-checked
(x86_64-pc-windows-gnu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
jared added 1 commit 2026-09-30 10:59:53 -04:00
jared merged commit 97ccc97a70 into main 2026-09-30 20:17:19 -04:00
Sign in to join this conversation.