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 <noreply@anthropic.com>
This commit is contained in:
File 2026-01-25 23:43:17 +01:00 committed by krejcif
parent b0b54d0fcd
commit a5a42ed3e2
3 changed files with 27 additions and 10 deletions

View File

@ -283,15 +283,15 @@ export class RuvectorDatabase implements RuvectorDB {
.map((r) => ({ .map((r) => ({
document: { document: {
id: r.id, id: r.id,
content: r.metadata.content as string, content: (r.metadata.content as string | undefined) ?? "",
vector: [], // Don't return vector to save memory vector: [], // Don't return vector to save memory
direction: r.metadata.direction as "inbound" | "outbound", direction: (r.metadata.direction as "inbound" | "outbound" | undefined) ?? "inbound",
channel: r.metadata.channel as string, channel: (r.metadata.channel as string | undefined) ?? "",
user: r.metadata.user as string | undefined, user: r.metadata.user as string | undefined,
conversationId: r.metadata.conversationId as string | undefined, conversationId: r.metadata.conversationId as string | undefined,
sessionKey: r.metadata.sessionKey as string | undefined, sessionKey: r.metadata.sessionKey as string | undefined,
agentId: r.metadata.agentId 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, metadata: r.metadata,
}, },
score: r.score, score: r.score,
@ -457,6 +457,11 @@ export class RuvectorDatabase implements RuvectorDB {
return this.findRelatedInMemory(id, relationship, depth); 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 // Use Cypher to find related nodes with their properties
const cypherQuery = relationship const cypherQuery = relationship
? `MATCH (a)-[r:${relationship}*1..${depth}]->(b:Message) WHERE a.id = $id RETURN DISTINCT b` ? `MATCH (a)-[r:${relationship}*1..${depth}]->(b:Message) WHERE a.id = $id RETURN DISTINCT b`

View File

@ -592,8 +592,10 @@ function registerLocalMode(api: ClawdbotPluginApi, config: RuvectorConfig): void
source: "graph", source: "graph",
}); });
} }
} catch { } catch (err) {
// Skip graph expansion errors // 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 { try {
const created = await graphDb.linkMessages(id, targetId, "RELATED_TO"); const created = await graphDb.linkMessages(id, targetId, "RELATED_TO");
if (created) edgesCreated++; if (created) edgesCreated++;
} catch { } catch (err) {
// Skip failed edges // 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) { for (const pattern of similarPatterns) {
// Pattern centroid contains [query, result], extract result portion // Pattern centroid contains [query, result], extract result portion
const dim = queryVector.length; 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); 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) { if (patternResultCentroid.length > 0 && result.document.vector.length > 0) {
const similarity = cosineSimilarity(result.document.vector, patternResultCentroid); const similarity = cosineSimilarity(result.document.vector, patternResultCentroid);
patternBoost += similarity * pattern.avgQuality * boostFactor; patternBoost += similarity * pattern.avgQuality * boostFactor;

View File

@ -603,8 +603,10 @@ export function createRuvectorRecallTool(options: CreateRuvectorRecallToolOption
relationship, relationship,
}); });
} }
} catch { } catch (err) {
// Skip entries that can't be fetched // 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}`);
} }
} }