diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index a205a399..c39b90e0 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -329,8 +329,8 @@ jobs: pdfium_version=$(sed -n '11p' metadata.txt) pdfium_version=$(echo $pdfium_version | cut -d'.' -f3) - # Next line is the Qdrant version: - qdrant_version="v$(sed -n '12p' metadata.txt)" + # Next line is the vector store version: + vector_store_version="$(sed -n '12p' metadata.txt)" # Write the metadata to the environment: echo "APP_VERSION=${app_version}" >> $GITHUB_ENV @@ -344,7 +344,7 @@ jobs: echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV echo "PDFIUM_VERSION=${pdfium_version}" >> $GITHUB_ENV - echo "QDRANT_VERSION=${qdrant_version}" >> $GITHUB_ENV + echo "VECTOR_STORE_VERSION=${vector_store_version}" >> $GITHUB_ENV # Log the metadata: echo "App version: '${formatted_app_version}'" @@ -357,7 +357,7 @@ jobs: echo "Tauri version: '${tauri_version}'" echo "Architecture: '${{ matrix.dotnet_runtime }}'" echo "PDFium version: '${pdfium_version}'" - echo "Qdrant version: '${qdrant_version}'" + echo "Vector store version: '${vector_store_version}'" - name: Read and format metadata (Windows) if: matrix.platform == 'windows-latest' @@ -402,8 +402,8 @@ jobs: $pdfium_version = $metadata[10] $pdfium_version = $pdfium_version.Split('.')[2] - # Next line is the necessary Qdrant version: - $qdrant_version = "v$($metadata[11])" + # Next line is the vector store version: + $vector_store_version = $metadata[11] # Write the metadata to the environment: Write-Output "APP_VERSION=${app_version}" >> $env:GITHUB_ENV @@ -416,7 +416,7 @@ jobs: Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV Write-Output "PDFIUM_VERSION=${pdfium_version}" >> $env:GITHUB_ENV - Write-Output "QDRANT_VERSION=${qdrant_version}" >> $env:GITHUB_ENV + Write-Output "VECTOR_STORE_VERSION=${vector_store_version}" >> $env:GITHUB_ENV # Log the metadata: Write-Output "App version: '${formatted_app_version}'" @@ -429,7 +429,7 @@ jobs: Write-Output "Tauri version: '${tauri_version}'" Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'" Write-Output "PDFium version: '${pdfium_version}'" - Write-Output "Qdrant version: '${qdrant_version}'" + Write-Output "Vector store version: '${vector_store_version}'" - name: Setup .NET uses: actions/setup-dotnet@v4 diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Databases.cs b/app/MindWork AI Studio/Tools/Services/RustService.Databases.cs index 7760421a..3f101d70 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Databases.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Databases.cs @@ -41,7 +41,7 @@ public sealed partial class RustService using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(TimeSpan.FromMinutes(5)); - var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token); + using var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token); response.EnsureSuccessStatusCode(); var operation = await response.Content.ReadFromJsonAsync(this.jsonRustSerializerOptions, cts.Token); diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index 311fe569..65751edc 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -66,16 +66,6 @@ "MudBlazor": "8.11.0" } }, - "Qdrant.Client": { - "type": "Direct", - "requested": "[1.18.1, )", - "resolved": "1.18.1", - "contentHash": "eBwFLihGMvN02/jr/BNdcop2XmtA10y8VMOclVZ7K2H8yheAhl7jbkf7I8e4X3RYpT+cAxgcalP4xmOhgs4KJg==", - "dependencies": { - "Google.Protobuf": "3.31.0", - "Grpc.Net.Client": "2.71.0" - } - }, "ReverseMarkdown": { "type": "Direct", "requested": "[5.0.0, )", @@ -90,33 +80,6 @@ "resolved": "3.2.449", "contentHash": "uA9sYDy4VepL3xwzBTLcP2LyuVYMt0ZIT3gaSiXvGoX15Ob+rOP+hGydhevlSVd+rFo+Y+VQFEHDuWU8HBW+XA==" }, - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.31.0", - "contentHash": "OZXSf6igaJBeo+kAzMhYF0R5zp0nRgf4G0Uis/IsGKACc4RGP9bQPLpHLengIFuASl0lY92utMB8rRpTx4TaOg==" - }, - "Grpc.Core.Api": { - "type": "Transitive", - "resolved": "2.71.0", - "contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg==" - }, - "Grpc.Net.Client": { - "type": "Transitive", - "resolved": "2.71.0", - "contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==", - "dependencies": { - "Grpc.Net.Common": "2.71.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0" - } - }, - "Grpc.Net.Common": { - "type": "Transitive", - "resolved": "2.71.0", - "contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==", - "dependencies": { - "Grpc.Core.Api": "2.71.0" - } - }, "LuaCSharp.Annotations": { "type": "Transitive", "resolved": "0.5.5", diff --git a/runtime/src/qdrant_edge_database.rs b/runtime/src/qdrant_edge_database.rs index 615a8a42..2e4f4d7c 100644 --- a/runtime/src/qdrant_edge_database.rs +++ b/runtime/src/qdrant_edge_database.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; use axum::Json; use log::{error, info, warn}; @@ -29,8 +29,8 @@ const VECTOR_INDEXING_THRESHOLD_KB: usize = 10_000; type QdrantEdgeResult = Result>; -static QDRANT_EDGE_DATABASE: Lazy>>> = - Lazy::new(|| Arc::new(Mutex::new(None))); +static QDRANT_EDGE_DATABASE: Lazy>> = + Lazy::new(|| Mutex::new(None)); static QDRANT_EDGE_STATUS: Lazy> = Lazy::new(|| Mutex::new(QdrantEdgeStatusInfo::default())); @@ -186,6 +186,7 @@ impl QdrantEdgeDatabase { } fn ensure_store_exists(&mut self, store_name: &str, vector_size: usize) -> QdrantEdgeResult<()> { + validate_vector_size(vector_size)?; self.get_or_create_store(store_name, vector_size)?; Ok(()) } @@ -196,6 +197,7 @@ impl QdrantEdgeDatabase { }; let vector_size = first_point.vector.len(); + validate_vector_size(vector_size)?; if points.iter().any(|point| point.vector.len() != vector_size) { return Err("All vectors in one insert request must have the same size.".into()); } @@ -241,10 +243,14 @@ impl QdrantEdgeDatabase { } } -fn qdrant_edge_base_path() -> PathBuf { - Path::new(DATA_DIRECTORY.get().unwrap()) +fn qdrant_edge_base_path() -> QdrantEdgeResult { + let data_directory = DATA_DIRECTORY + .get() + .ok_or("The data directory has not been initialized.")?; + + Ok(Path::new(data_directory) .join("databases") - .join("vector_database") + .join("vector_database")) } pub async fn qdrant_edge_info(_token: APIToken) -> Json { @@ -298,7 +304,16 @@ pub fn start_qdrant_edge_database(app_handle: tauri::AppHandl set_qdrant_edge_starting(); remove_obsolete_qdrant_sidecar_files(&app_handle); - let path = qdrant_edge_base_path(); + let path = match qdrant_edge_base_path() { + Ok(path) => path, + Err(e) => { + let reason = format!("Qdrant Edge cannot be started: {e}"); + error!(Source = "Qdrant Edge"; "{reason}"); + set_qdrant_edge_unavailable(reason); + return; + }, + }; + match fs::create_dir_all(&path) { Ok(_) => { let database = QdrantEdgeDatabase::new(path.clone()); @@ -457,6 +472,14 @@ fn has_existing_store(path: &Path) -> bool { path.join("edge_config.json").exists() || path.join("segments").exists() } +fn validate_vector_size(vector_size: usize) -> QdrantEdgeResult<()> { + if vector_size == 0 { + return Err("Vector size must be greater than zero.".into()); + } + + Ok(()) +} + fn vector_store_version() -> QdrantEdgeResult { let metadata = META_DATA .lock() @@ -526,6 +549,10 @@ fn validate_store_name(store_name: &str) -> QdrantEdgeResult<()> { return Err("Vector store name cannot be empty.".into()); } + if matches!(store_name, "." | "..") { + return Err(format!("Vector store name '{store_name}' is not supported.").into()); + } + if store_name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.') @@ -535,3 +562,19 @@ fn validate_store_name(store_name: &str) -> QdrantEdgeResult<()> { Err(format!("Vector store name '{store_name}' contains unsupported characters.").into()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validate_store_name_allows_safe_store_names() { + assert!(validate_store_name("rag_1234-abcd.ef").is_ok()); + } + + #[test] + fn validate_store_name_rejects_path_traversal_names() { + assert!(validate_store_name(".").is_err()); + assert!(validate_store_name("..").is_err()); + } +}