T02 · Agent Memory Poisoning
- Location
index.js:381- Finding
Persistent Prompt Injection Through Unsanitized Memory Recall
- Content
View full analysis
Vulnerability Details
File Location:
extractor.js:17-30,index.js:117-145, andindex.js:349-387
Vulnerability Type: Persistent memory poisoning and trust-boundary violation
Risk Level: HighComplete Vulnerable Code
extractor.js:17-30stores qualifying user-controlled text verbatim:javascript async extractFromUserMessage(content) { const trimmed = content.trim(); if (trimmed.length < 3) { return null; } // 使用 embedding 模型提取标签 const extractedTags = await this.embeddingModel.extractTags(trimmed); // 根据标签确定知识类型和重要性 const { knowledgeType, importance } = this.determineKnowledgeType(extractedTags, trimmed); if (!knowledgeType) { return null; } return { shouldExtract: true, content: trimmed, knowledgeType, importance, tags: [...extractedTags, 'user'] }; }index.js:349-387retrieves the stored content and places it directly into system-level context:javascript async searchMemoriesForContext(engine, userContent) { const start = Date.now(); this.stats.searches += 1; const extractor = engine.extractor; const embeddingModel = extractor.embeddingModel; const queryEmbedding = await this.getCachedEmbedding(`q:${userContent}`, userContent, embeddingModel); const allMemories = await engine.getAllMemories(this.memoryFetchLimit); debugLog('🔍 获取到记忆数量:', allMemories?.length); if (!allMemories || allMemories.length === 0) { this.recordSearchStats(start, 0); return { systemPromptAddition: '', activeRecallPrompts: [] }; } const keywordHits = this.keywordRecall(allMemories, userContent); const results = await Promise.all(allMemories.map(async (memory) => { const memoryKey = memory.id || memory.content; const memoryEmbedding = memory.embedding || await this.getC ...[truncated 4453 chars]- Remediation
View remediation
Remediation Suggestions
- Never concatenate recalled memory directly into the system instruction channel. Supply it through a clearly delimited, lower-trust data structure or user-context field.
- Explicitly state outside the untrusted data boundary that recalled records are quotations and must not be followed as instructions.
- Store structured facts rather than unrestricted message text. Separate subject, predicate, value, provenance, author, and creation time.
- Detect and reject instruction-like content, including role declarations, policy overrides, tool directives, requests to reveal secrets, and phrases attempting to ignore previous instructions.
- Require explicit user confirmation before automatically persisting content, especially when the content does not match a narrowly defined memory schema.
- Preserve provenance and trust level for every memory. Never promote user-authored records to system authority.
- Validate recalled content again before insertion because previously stored databases may already contain poisoned records.
- Add tests demonstrating that stored strings such as “ignore previous instructions” remain quoted data and cannot alter Agent behavior.
- Provide memory review and permanent deletion functions so users can remove poisoned records.
