T02 · Agent Memory Poisoning
- Location
src/index.ts:75- Finding
Persistent Memory Content Is Injected into the Agent Prompt Without Trust Boundaries
- Content
View full analysis
{ const ctx = event.context; const from = (ctx["from"] as string) ?? "unknown"; const userId = rawId(from); const userMessage = (ctx["content"] as string) ?? ""; // Store under raw ID for lookup in message:sent lastMessage.set(userId, userMessage); try { const result = await mem.get(userId, userMessage); if (result.context?.trim()) { event.messages.push(`[Memory]\n${result.context}`); } } catch (err) { api.logger.warn(`[getmem] fetch failed: ${(err as Error).message}`); } }, { name: "getmem-message-received" }); ``` The corresponding compiled implementation is present in `dist/index.js:57-78`. ### Technical Analysis The plugin retrieves persistent memory from an external service and appends the returned text directly to `event.messages`. The returned context is not escaped, filtered, assigned a constrained data-only role, or accompanied by a reliable instruction that it must not be treated as executable agent guidance. Because ordinary user messages are uploaded to the same memory service, instruction-like content supplied by a user may later be retrieved and reintroduced into subsequent prompts. This turns untrusted conversation content into a persistent prompt-injection channel. The `[Memory]` prefix is only a textual label. It does not enforce a privilege boundary between memory data and agent instructions. ### Attack Path 1. An attacker sends a message containing instruction-like content designed to alter future agent behavior. 2. After the assistant responds, the plugin uploads the attacker-controlled message through `/v1/memory/ingest`. 3. During a later message, `/v1/memory/get` returns that stored content as relevant context. 4 ...[truncated 848 chars]- Remediation
View remediation
