T02 · Agent Memory Poisoning
Error
- Location
- index.js:203
- Finding
- Persistent prompt injection through automatically stored and recalled messages<![CDATA[ ## Vulnerability Details **File Location**: `index.js:203-223` **Vulnerability Type**: Persistent memory poisoning and prompt injection **Risk Level**: High ### Vulnerable Code ```javascript async onMessage(userMessage, sessionId) { for (const cmd of this.commands) { if (userMessage === cmd.trigger || userMessage.startsWith(cmd.trigger + ' ')) { return await cmd.handler(userMessage, sessionId); } } if (userMessage.length < 15) return userMessage; const context = await this.recall(userMessage, this.recallLimit); this.remember(userMessage, ['role:user', `session:${sessionId}`]).catch(() => {}); if (context.length > 0) { // Filter out previously injected context blocks const filtered = context.filter(m => !m.observation.includes('<relevant-memories>')); if (filtered.length > 0) { const contextBlock = filtered.map(m => `- ${m.observation}`).join('\n'); return `[Recalled Memory]\n${contextBlock}\n\nUser: ${userMessage}`; } } return userMessage; } ``` The related response-saving behavior at `index.js:227-237` also accepts agent-generated text without validating whether it contains instructions: ```javascript async onResponse(agentResponse, sessionId) { if (!this.apiKey) return; if (!agentResponse || agentResponse.length < 20) return; // Skip responses that contain injected memory context if (agentResponse.includes('<relevant-memories>')) return; const observation = agentResponse.length > 500 ? agentResponse.slice(0, 500) + '...' : agentResponse; this.remember(observation, ['role:assistant', `session:${sessionId}`]).catch(() => {}); } ``` ### Technical Analysis Every non-command user message of at least 15 characters is automatically persisted. Recalled observations are subsequently concatenated directly into the text sent to the downstream language model. The recalled content is not escaped, assigned a trustworthy provenance level, structurally separated from instructio ...[truncated 2781 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Pass recalled memories through a separately typed, non-instruction data channel when the host framework supports structured context. 2. If plain-text prompt composition is unavoidable, prepend a trusted instruction explicitly stating that recalled memories are untrusted historical data and must never override system, developer, safety, authorization, or tool-use rules. 3. Place every observation inside a robust structured encoding, such as a JSON object with explicit `content`, `source`, `timestamp`, and `trust_level` fields. Do not rely on informal headings alone. 4. Detect and quarantine observations containing instruction-like language, role markers, prompt delimiters, hidden Unicode controls, or requests to override earlier instructions. 5. Preserve provenance and access scope for each memory. Memories originating from shared spaces, external users, or model-generated content should receive lower trust than administrator-approved facts. 6. Require review or explicit opt-in before untrusted shared memories can be injected into an agent with sensitive tools. 7. Make automatic saving opt-in for sensitive deployments and provide per-message controls to prevent secrets or untrusted content from being persisted. 8. Correct the inconsistent feedback-loop markers and use one canonical representation. This should supplement, not replace, actual prompt-injection defenses. 9. Add adversarial tests covering persistent instructions, role spoofing, delimiter injection, shared-memory poisoning, and malicious content reproduced in agent responses. ]]>
