diff --git a/apps/tauri/src-tauri/tauri.conf.json b/apps/tauri/src-tauri/tauri.conf.json index 1613341..83a6a4b 100644 --- a/apps/tauri/src-tauri/tauri.conf.json +++ b/apps/tauri/src-tauri/tauri.conf.json @@ -23,7 +23,7 @@ } ], "security": { - "csp": null + "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com; connect-src ipc: http://ipc.localhost" } }, "bundle": { diff --git a/crates/onyx-cli/src/commands/sync.rs b/crates/onyx-cli/src/commands/sync.rs index 234062d..a20d0c2 100644 --- a/crates/onyx-cli/src/commands/sync.rs +++ b/crates/onyx-cli/src/commands/sync.rs @@ -204,18 +204,20 @@ fn print_workspace_status(name: &str, path: &std::path::Path, webdav_url: Option Ok(()) } -/// Extract domain from a URL for credential storage. +/// Extract host from a URL for credential storage. fn extract_domain(url: &str) -> String { - url.split("://") - .nth(1) - .unwrap_or(url) - .split('/') - .next() - .unwrap_or(url) - .split(':') - .next() - .unwrap_or(url) - .to_string() + // Strip scheme + let after_scheme = url.split("://").nth(1).unwrap_or(url); + // Strip path + let authority = after_scheme.split('/').next().unwrap_or(after_scheme); + // Strip userinfo (user:pass@host) + let host_port = if let Some(at_pos) = authority.rfind('@') { + &authority[at_pos + 1..] + } else { + authority + }; + // Strip port + host_port.split(':').next().unwrap_or(host_port).to_string() } /// Prompt the user for text input. diff --git a/crates/onyx-core/src/storage.rs b/crates/onyx-core/src/storage.rs index 62bb4a2..9a41991 100644 --- a/crates/onyx-core/src/storage.rs +++ b/crates/onyx-core/src/storage.rs @@ -149,8 +149,30 @@ impl FileSystemStorage { Err(Error::ListNotFound(list_id.to_string())) } - fn list_dir_path_by_name(&self, name: &str) -> PathBuf { - self.root_path.join(name) + fn list_dir_path_by_name(&self, name: &str) -> Result { + let path = self.root_path.join(name); + // Prevent path traversal: resolved path must stay within root + let canonical_root = self.root_path.canonicalize() + .unwrap_or_else(|_| self.root_path.clone()); + let canonical_path = if path.exists() { + path.canonicalize().unwrap_or_else(|_| path.clone()) + } else { + // For non-existent paths, normalize by resolving the parent + if let Some(parent) = path.parent() { + let canonical_parent = if parent.exists() { + parent.canonicalize().unwrap_or_else(|_| parent.to_path_buf()) + } else { + parent.to_path_buf() + }; + canonical_parent.join(path.file_name().unwrap_or_default()) + } else { + path.clone() + } + }; + if !canonical_path.starts_with(&canonical_root) { + return Err(Error::InvalidData(format!("Invalid list name: path escapes workspace"))); + } + Ok(path) } fn sanitize_filename(name: &str) -> String { @@ -371,7 +393,7 @@ impl Storage for FileSystemStorage { } fn create_list(&mut self, name: String) -> Result { - let list_dir = self.list_dir_path_by_name(&name); + let list_dir = self.list_dir_path_by_name(&name)?; if list_dir.exists() { return Err(Error::InvalidData(format!("List '{}' already exists", name))); @@ -473,7 +495,7 @@ impl Storage for FileSystemStorage { fn rename_list(&mut self, list_id: Uuid, new_name: String) -> Result<()> { let old_dir = self.list_dir_path(list_id)?; - let new_dir = self.list_dir_path_by_name(&new_name); + let new_dir = self.list_dir_path_by_name(&new_name)?; if new_dir.exists() { return Err(Error::InvalidData(format!("A list named '{}' already exists", new_name)));