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:
parent
b0b54d0fcd
commit
a5a42ed3e2
@ -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`
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user