Enabled WebKit permission handling for Linux audio capture (#854)
Build and Release / Determine run mode (push) Has been cancelled
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Sync Flatpak repo (push) Has been cancelled
Build and Release / Collect Flatpak artifacts (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-07-13 17:15:55 +02:00
1 parent 50d7b56ac0
commit 5dc6282908
5 files changed
+114 -1

No files matched your search

+111
View File
@@ -30,9 +30,16 @@ use crate::environment::{
use crate::log::switch_to_file_logging;
use crate::pdfium::PDFIUM_LIB_PATH;
use crate::qdrant_edge_database::{start_qdrant_edge_database, stop_qdrant_edge_database};
#[cfg(debug_assertions)]
use crate::dotnet::create_startup_env_file;
#[cfg(target_os = "linux")]
use webkit2gtk::glib::Cast;
#[cfg(target_os = "linux")]
use webkit2gtk::{PermissionRequestExt, UserMediaPermissionRequestExt};
/// The Tauri main window.
pub static MAIN_WINDOW: Lazy<Mutex<Option<WebviewWindow>>> = Lazy::new(|| Mutex::new(None));
@@ -138,6 +145,9 @@ pub fn start_tauri() {
// Get the main window:
let window = app.get_webview_window("main").expect("Failed to get main window.");
#[cfg(target_os = "linux")]
register_linux_permission_request_handler(&window);
// Register a callback for window events, such as file drops. We have to use
// this handler in addition to the app event handler, because file drop events
// are only available in the window event handler (is a bug, cf. https://github.com/tauri-apps/tauri/issues/14338):
@@ -247,6 +257,69 @@ fn same_origin(left: &tauri::Url, right: &tauri::Url) -> bool {
&& left.port_or_known_default() == right.port_or_known_default()
}
#[cfg(any(target_os = "linux", test))]
fn should_allow_audio_capture(
approved_app_url: Option<&tauri::Url>,
current_webview_url: Option<&tauri::Url>,
requests_audio: bool,
requests_video: bool,
) -> bool {
requests_audio
&& !requests_video
&& approved_app_url.is_some_and(is_local_http_url)
&& approved_app_url
.zip(current_webview_url)
.is_some_and(|(approved, current)| same_origin(approved, current))
}
#[cfg(target_os = "linux")]
fn register_linux_permission_request_handler(window: &WebviewWindow) {
if let Err(error) = window.with_webview(|platform_webview| {
use webkit2gtk::WebViewExt;
use webkit2gtk::UserMediaPermissionRequest;
let webview = platform_webview.inner();
webview.connect_permission_request(|webview, request| {
let Some(user_media_request) = request.downcast_ref::<UserMediaPermissionRequest>() else {
request.deny();
info!(Source = "Tauri"; "Denied a non-user-media WebKit permission request.");
return true;
};
let current_webview_url = webview
.uri()
.and_then(|uri| tauri::Url::parse(uri.as_str()).ok());
let approved_app_url = APPROVED_APP_URL.lock().unwrap().clone();
let origin_matches = approved_app_url
.as_ref()
.zip(current_webview_url.as_ref())
.is_some_and(|(approved, current)| same_origin(approved, current));
let requests_audio = user_media_request.is_for_audio_device();
let requests_video = user_media_request.is_for_video_device();
let allow = should_allow_audio_capture(
approved_app_url.as_ref(),
current_webview_url.as_ref(),
requests_audio,
requests_video,
);
if allow {
request.allow();
} else {
request.deny();
}
info!(
Source = "Tauri";
"Handled WebKit user-media permission request: allowed={allow}, origin_matches={origin_matches}, audio={requests_audio}, video={requests_video}."
);
true
});
}) {
error!(Source = "Tauri"; "Failed to register the Linux WebKit permission request handler: {error}");
}
}
fn should_open_in_system_browser<R: tauri::Runtime>(webview: &tauri::Webview<R>, url: &tauri::Url) -> bool {
match url.scheme() {
"mailto" | "tel" => return true,
@@ -1139,4 +1212,42 @@ mod tests {
assert!(!is_tauri_asset_url(&url));
assert!(!is_local_http_url(&url));
}
#[test]
fn audio_capture_is_allowed_for_exact_approved_app_origin() {
let approved = tauri::Url::parse("http://localhost:12345/").unwrap();
let current = tauri::Url::parse("http://localhost:12345/voice-recorder").unwrap();
assert!(should_allow_audio_capture(Some(&approved), Some(&current), true, false));
}
#[test]
fn audio_capture_is_denied_for_wrong_port() {
let approved = tauri::Url::parse("http://localhost:12345/").unwrap();
let current = tauri::Url::parse("http://localhost:54321/").unwrap();
assert!(!should_allow_audio_capture(Some(&approved), Some(&current), true, false));
}
#[test]
fn audio_capture_is_denied_for_external_origin() {
let approved = tauri::Url::parse("http://localhost:12345/").unwrap();
let current = tauri::Url::parse("https://example.com/").unwrap();
assert!(!should_allow_audio_capture(Some(&approved), Some(&current), true, false));
}
#[test]
fn video_capture_is_denied() {
let approved = tauri::Url::parse("http://localhost:12345/").unwrap();
assert!(!should_allow_audio_capture(Some(&approved), Some(&approved), false, true));
}
#[test]
fn combined_audio_and_video_capture_is_denied() {
let approved = tauri::Url::parse("http://localhost:12345/").unwrap();
assert!(!should_allow_audio_capture(Some(&approved), Some(&approved), true, true));
}
}