From a5a42ed3e2e067fc735c84f81379cb1d4fb19265 Mon Sep 17 00:00:00 2001 From: File Date: Sun, 25 Jan 2026 23:43:17 +0100 Subject: [PATCH] fix(memory-ruvector): address code review security and robustness issues - Add Cypher injection protection: validate relationship type is alphanumeric - Fix metadata type coercion: use nullish coalescing for optional fields - Add pattern centroid validation: skip malformed patterns in boosting - Add debug logging to silent catches for better troubleshooting Co-Authored-By: Claude Opus 4.5 --- extensions/memory-ruvector/db.ts | 13 +++++++++---- extensions/memory-ruvector/index.ts | 18 ++++++++++++++---- extensions/memory-ruvector/tool.ts | 6 ++++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/extensions/memory-ruvector/db.ts b/extensions/memory-ruvector/db.ts index b59864466..e87daab79 100644 --- a/extensions/memory-ruvector/db.ts +++ b/extensions/memory-ruvector/db.ts @@ -283,15 +283,15 @@ export class RuvectorDatabase implements RuvectorDB { .map((r) => ({ document: { id: r.id, - content: r.metadata.content as string, + content: (r.metadata.content as string | undefined) ?? "", vector: [], // Don't return vector to save memory - direction: r.metadata.direction as "inbound" | "outbound", - channel: r.metadata.channel as string, + direction: (r.metadata.direction as "inbound" | "outbound" | undefined) ?? "inbound", + channel: (r.metadata.channel as string | undefined) ?? "", user: r.metadata.user as string | undefined, conversationId: r.metadata.conversationId as string | undefined, sessionKey: r.metadata.sessionKey as string | undefined, agentId: r.metadata.agentId as string | undefined, - timestamp: r.metadata.timestamp as number, + timestamp: (r.metadata.timestamp as number | undefined) ?? Date.now(), metadata: r.metadata, }, score: r.score, @@ -457,6 +457,11 @@ export class RuvectorDatabase implements RuvectorDB { return this.findRelatedInMemory(id, relationship, depth); } + // Validate relationship to prevent Cypher injection (allow alphanumeric and underscore only) + if (relationship && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(relationship)) { + throw new Error(`ruvector: invalid relationship type "${relationship}" - must be alphanumeric`); + } + // Use Cypher to find related nodes with their properties const cypherQuery = relationship ? `MATCH (a)-[r:${relationship}*1..${depth}]->(b:Message) WHERE a.id = $id RETURN DISTINCT b` diff --git a/extensions/memory-ruvector/index.ts b/extensions/memory-ruvector/index.ts index 0e61883fd..6f70783cf 100644 --- a/extensions/memory-ruvector/index.ts +++ b/extensions/memory-ruvector/index.ts @@ -592,8 +592,10 @@ function registerLocalMode(api: ClawdbotPluginApi, config: RuvectorConfig): void source: "graph", }); } - } catch { - // Skip graph expansion errors + } catch (err) { + // Skip graph expansion errors but log for debugging + const msg = err instanceof Error ? err.message : String(err); + api.logger.debug?.(`ruvector_recall: graph expansion failed: ${msg}`); } } @@ -772,8 +774,10 @@ function registerLocalMode(api: ClawdbotPluginApi, config: RuvectorConfig): void try { const created = await graphDb.linkMessages(id, targetId, "RELATED_TO"); if (created) edgesCreated++; - } catch { - // Skip failed edges + } catch (err) { + // Skip failed edges but log for debugging + const msg = err instanceof Error ? err.message : String(err); + api.logger.debug?.(`ruvector_learn: failed to create edge to ${targetId}: ${msg}`); } } } @@ -1294,8 +1298,14 @@ function rerankWithPatterns( for (const pattern of similarPatterns) { // Pattern centroid contains [query, result], extract result portion const dim = queryVector.length; + // Validate centroid has expected structure before slicing + if (pattern.centroid.length < dim * 2) { + continue; // Skip malformed pattern + } const patternResultCentroid = pattern.centroid.slice(dim, dim * 2); + // result.document.vector is intentionally [] to save memory, so pattern boosting + // only works when vectors are available (e.g., from graph expansion with vectors) if (patternResultCentroid.length > 0 && result.document.vector.length > 0) { const similarity = cosineSimilarity(result.document.vector, patternResultCentroid); patternBoost += similarity * pattern.avgQuality * boostFactor; diff --git a/extensions/memory-ruvector/tool.ts b/extensions/memory-ruvector/tool.ts index 83dbda034..ec46615b6 100644 --- a/extensions/memory-ruvector/tool.ts +++ b/extensions/memory-ruvector/tool.ts @@ -603,8 +603,10 @@ export function createRuvectorRecallTool(options: CreateRuvectorRecallToolOption relationship, }); } - } catch { - // Skip entries that can't be fetched + } catch (err) { + // Skip entries that can't be fetched but log for debugging + const errMsg = err instanceof Error ? err.message : String(err); + api.logger.debug?.(`ruvector_recall: failed to fetch graph entry ${id}: ${errMsg}`); } }