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.
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>).
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.
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
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
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.
Fixes #22: permission requests are granted only to the app's own origin.
Before
request.allow()for every request of every kind (mic, camera, location, clipboard, storage access, pointer lock, DRM, …), whatever page was loaded.After
The policy lives in
src-tauri/src/webview_permissions.rsand is unit-tested.http://localhost:44548)tauri://localhost,http://tauri.localhost) anddevUrl, sotauri devstill works.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 givesmicrophone; cameraonly to the call frame, which on desktop is always the same-origin bundled Element Call.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.
allow=allow=allow="microphone"(Location "allowed" means it got past permission, then failed with "no geolocation service" in the container.)
navigator.mediaDevicesis present, the notification shim reportsgranted, no denials logged. Clipboard write + read-back (the #156 recovery-key wipe) still works, though that was checked under WebDriver only.cargo test --lib16 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