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) => ({
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`

View File

@ -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;

View File

@ -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}`);
}
}