T02 · Agent Memory Poisoning
Error
- Location
- index.js:28
- Finding
- Persistent Agent Memory Poisoning Through Untrusted Lesson Content<![CDATA[ ## Vulnerability Details **File Location**: `index.js:28`, `index.js:58-62`, `index.js:74-83`, and `index.js:123-128` **Vulnerability Type**: Persistent storage of untrusted instructions **Risk Level**: High ### Vulnerable Code ```js const MEMORY_FILE = ARGS.file ? path.resolve(ARGS.file) : path.resolve(__dirname, '../../MEMORY.md'); ``` ```js function internalize(text, category, id) { if (!text || !category) { console.error("Error: --text and --category are required for 'internalize'."); process.exit(1); } if (!fs.existsSync(MEMORY_FILE)) { console.error("Error: MEMORY.md not found."); process.exit(1); } ``` ```js let content = fs.readFileSync(MEMORY_FILE, 'utf8'); // Check if ID already exists if (content.includes(`| **${id}** |`)) { console.log(`Entry ${id} already exists in MEMORY.md. Skipping.`); return; } // 1. Prepare Table Row // | ID | Type | Category | Summary | ~Tok | const tokens = Math.ceil(text.length / 4); // Extract first sentence or first 50 chars for summary const summaryLine = text.split('\n')[0].replace(/[|]/g, '-'); // Escape pipes const summary = summaryLine.substring(0, 50) + (summaryLine.length > 50 ? '...' : ''); const type = "Lesson"; ``` ```js const detailEntry = `\n### ${id} | ${category} | ${summary}\n**Date:** ${new Date().toISOString().split('T')[0]}\n${text}\n`; content += detailEntry; fs.writeFileSync(MEMORY_FILE, content); console.log(`Successfully internalized ${id} to MEMORY.md (Index + Detail)`); ``` ### Technical Analysis The `internalize` command treats the caller-controlled `text`, `category`, and `id` arguments as trusted memory content and stores them in `MEMORY.md`. The implementation does not distinguish factual lesson data from executable natural-language instructions. Only pipe characters in the first-line summary are replaced. The complete `text` value is appended without sanitization, and `category` and `id` are interpolated into Markdown headings and table content ...[truncated 1906 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Require explicit human confirmation before persisting any lesson originating from an external or untrusted source. 2. Store lessons as structured records with provenance, author identity, trust level, creation time, and approval state. 3. Treat persisted lesson text as quoted data, never as authoritative instructions. Ensure consuming agents are explicitly instructed not to execute directives embedded in memory entries. 4. Validate `id` and `category` against restrictive allowlists, such as: - `id`: `^[A-Z][0-9]+$` - `category`: a fixed set of supported categories 5. Escape Markdown metacharacters and prevent user input from creating headings, tables, comments, or other structural content. 6. Detect and quarantine instruction-like content, including requests to ignore prior rules, expose secrets, invoke tools, or modify security policy. 7. Separate untrusted observations from trusted policy memory. Untrusted records should require review before promotion into long-term trusted memory. 8. Add tests covering multiline prompt injection, forged headings, Markdown structure injection, and attempts to redefine agent behavior. ]]>
