From 8c941a6b722e450219360706c57dfbfa77d199cf Mon Sep 17 00:00:00 2001 From: M1noa Date: Thu, 16 Jul 2026 17:41:38 -0500 Subject: [PATCH] fix(LCEL-01): disable arbitrary-path IPC + remove opener ** scope three changes: 1. file_dialogs::write_binary_file / read_binary_file used to accept arbitrary paths with no validation, no dialog, no scope. now they refuse all paths. callers must use pick_file / save_file_dialog. 2. plugins::list_directory used to accept arbitrary paths with no validation. now it refuses all paths. callers must use get_plugins_dir. 3. capabilities/default.json had opener:allow-open-path scoped to path: "**" which let the webview launch ANY local path. removed that entry entirely. opener:allow-reveal-item-in-dir is left for now (used by the file manager UI). combined, these close the webview -> arbitrary write+execute primitive. the proper fix is to scope opener:allow-open-path to a specific games directory once we know what paths the launcher actually needs to open. --- src-tauri/capabilities/default.json | 15 +++++++-------- src-tauri/src/commands/file_dialogs.rs | 16 ++++++++++++---- src-tauri/src/commands/plugins.rs | 17 +++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 419bf65..c9cae75 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -23,14 +23,13 @@ "gamepad:default", "drpc:default", "updater:default", - { - "identifier": "opener:allow-open-path", - "allow": [ - { - "path": "**" - } - ] - }, + // security: removed opener:allow-open-path with path: "**" - that + // scoped the webview to launch ANY local path. combined with + // write_binary_file + list_directory (now disabled, see file_dialogs.rs + // and plugins.rs) a compromised webview had a full RCE primitive. + // (LCEL-01) + // TODO: scope to a specific games directory (e.g. the instance dir) + // once we know what paths the launcher actually needs to open. { "identifier": "opener:allow-reveal-item-in-dir", "allow": [ diff --git a/src-tauri/src/commands/file_dialogs.rs b/src-tauri/src/commands/file_dialogs.rs index cff7e80..16003c6 100644 --- a/src-tauri/src/commands/file_dialogs.rs +++ b/src-tauri/src/commands/file_dialogs.rs @@ -40,12 +40,20 @@ pub fn save_file_dialog(title: String, filename: String, filters: Vec) - } } +// security: write_binary_file / read_binary_file used to accept arbitrary +// paths with no validation, no dialog, no scope. combined with the +// opener:allow-open-path: ** capability, a compromised webview could +// write+launch any file anywhere. (LCEL-01) +// TODO: scope these to a launcher-specific subdirectory with canonical +// containment checks, or remove from the IPC surface entirely and route +// all binary file ops through rfd-gated dialogs. +// for now, refuse all paths. callers must use pick_file / save_file_dialog. #[tauri::command] -pub fn write_binary_file(path: String, data: Vec) -> Result<(), String> { - fs::write(path, data).map_err(|e| e.to_string()) +pub fn write_binary_file(_path: String, _data: Vec) -> Result<(), String> { + Err("write_binary_file disabled: use save_file_dialog instead".into()) } #[tauri::command] -pub fn read_binary_file(path: String) -> Result, String> { - fs::read(path).map_err(|e| e.to_string()) +pub fn read_binary_file(_path: String) -> Result, String> { + Err("read_binary_file disabled: use pick_file instead".into()) } diff --git a/src-tauri/src/commands/plugins.rs b/src-tauri/src/commands/plugins.rs index 9fdf26a..91fa33c 100644 --- a/src-tauri/src/commands/plugins.rs +++ b/src-tauri/src/commands/plugins.rs @@ -15,16 +15,13 @@ pub fn get_plugins_dir(app: tauri::AppHandle) -> Result { #[tauri::command] pub fn list_directory(path: String) -> Result, String> { - let entries = fs::read_dir(&path).map_err(|e| e.to_string())?; - let mut results = Vec::new(); - for entry in entries { - let entry = entry.map_err(|e| e.to_string())?; - results.push(DirEntry { - name: entry.file_name().to_string_lossy().to_string(), - is_dir: entry.file_type().map(|t| t.is_dir()).unwrap_or(false), - }); - } - Ok(results) + // security: list_directory used to accept arbitrary paths with no + // validation. combined with write_binary_file + opener:allow-open-path: ** + // a compromised webview could enumerate startup folders, drop a binary, + // and launch it. (LCEL-01) + // TODO: scope to the launcher's plugins dir or remove from IPC entirely. + // for now, refuse all paths. + Err("list_directory disabled: use get_plugins_dir instead".into()) } #[tauri::command]