diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 7f5d403..e275932 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -41,6 +41,33 @@ fn lock_state(state: &Mutex) -> Result Result<(), String> { + let p = PathBuf::from(path); + // Reject obviously dangerous paths + let normalized = p.to_string_lossy(); + if normalized.is_empty() { + return Err("Workspace path cannot be empty".into()); + } + // Reject paths that are system root directories + #[cfg(unix)] + { + let forbidden = ["/", "/etc", "/usr", "/bin", "/sbin", "/var", "/proc", "/sys", "/dev"]; + let canonical = normalized.trim_end_matches('/'); + if forbidden.contains(&canonical) { + return Err(format!("Cannot use system directory as workspace: {}", path)); + } + } + #[cfg(windows)] + { + let upper = normalized.to_uppercase(); + if upper.len() <= 3 && upper.ends_with(":\\") || upper.ends_with(":") { + return Err(format!("Cannot use drive root as workspace: {}", path)); + } + } + Ok(()) +} + /// Serializable sync result for the frontend. #[derive(Debug, Serialize, Deserialize, Clone)] struct SyncResult { @@ -120,6 +147,7 @@ fn add_workspace( path: String, state: State<'_, Mutex>, ) -> Result<(), String> { + validate_workspace_path(&path)?; let mut s = lock_state(&state)?; let ws = WorkspaceConfig::new(name, PathBuf::from(&path)); let id = s.config.add_workspace(ws); @@ -243,6 +271,7 @@ async fn rename_workspace( #[tauri::command] fn init_workspace(path: String) -> Result<(), String> { + validate_workspace_path(&path)?; TaskRepository::init(PathBuf::from(path)) .map(|_| ()) .map_err(|e| e.to_string()) @@ -625,7 +654,7 @@ async fn create_remote_workspace( } else { format!("{}/{}", path.trim_end_matches('/'), ".onyx-workspace.json") }; - client.put_file(&file_path, serde_json::to_string_pretty(&metadata).unwrap().into_bytes()) + client.put_file(&file_path, serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?.into_bytes()) .await .map_err(|e| e.to_string())?; Ok(()) diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index 17f4958..a56cc53 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -26,7 +26,9 @@ invoke<[string, string]>("load_credentials", { domain }).then(([u, p]) => { webdavUser = u; webdavPass = p; - }).catch(() => {}); + }).catch((e) => { + console.warn("Failed to load credentials:", e); + }); } catch {} }); diff --git a/apps/tauri/src/lib/screens/TasksScreen.svelte b/apps/tauri/src/lib/screens/TasksScreen.svelte index 2f68d3b..bc84859 100644 --- a/apps/tauri/src/lib/screens/TasksScreen.svelte +++ b/apps/tauri/src/lib/screens/TasksScreen.svelte @@ -18,6 +18,13 @@ let parentTask = $derived(taskStack.length >= 1 ? app.tasks.find(t => t.id === taskStack[0]) ?? null : null); let subtaskDetail = $derived(taskStack.length >= 2 ? app.tasks.find(t => t.id === taskStack[1]) ?? null : null); + // Clear taskStack when the viewed task no longer exists (e.g. deleted or list switched) + $effect(() => { + if (taskStack.length > 0 && !parentTask) { + taskStack = []; + } + }); + function openTask(task: Task) { taskStack = [task.id]; } @@ -282,7 +289,7 @@
{#each app.lists as list (list.id)}