T02 · Agent Memory Poisoning
Warning
- Location
- scripts/track.js:117
- Finding
- Untrusted Transcript Content Can Trigger Persistent Memory Promotion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/track.js:117-129`, `scripts/maintain.js:68-80`, `scripts/maintain.js:171-186`, `scripts/maintain.js:228-252` **Vulnerability Type**: Untrusted access metadata influencing persistent agent memory **Risk Level**: Medium ### Vulnerable Code `scripts/track.js:117-129`: ```js // Also check tool results that contain memory file content if ((role === 'tool' || role === 'toolResult') && (message.content || entry.content)) { const rawContent = message.content || entry.content; const content = typeof rawContent === 'string' ? rawContent : JSON.stringify(rawContent); // Check if search results reference memory files const matches = content.match(/(?:MEMORY\.md|tier[23]-\w+\.md|\d{4}-\d{2}-\d{2}\.md)/g); if (matches) { for (const m of [...new Set(matches)]) { accesses.push({ type: 'search_result', file: m.includes('/') ? m : `memory/${m}`, timestamp, }); } } } ``` `scripts/maintain.js:68-80`: ```js function getLastAccess(accessLog, filePath, sectionTitle) { const normFile = filePath.replace(WORKSPACE + '/', ''); // Check section-level access const sectionKey = `${normFile}#${sectionTitle}`; if (accessLog.sections[sectionKey]) { return new Date(accessLog.sections[sectionKey].lastAccessed).getTime(); } // Fall back to file-level access if (accessLog.files[normFile]) { return new Date(accessLog.files[normFile].lastAccessed).getTime(); } return 0; // never accessed } ``` `scripts/maintain.js:171-186`: ```js // === PROMOTION: Tier 2/3 → Tier 1 (accessed in last 24h) === const promotionCutoff = now - (24 * 60 * 60 * 1000); for (const tier of [2, 3]) { for (const section of tiers[tier].sections) { const lastAccess = getLastAccess(accessLog, TIER_FILES[tier], section.title); if (lastAccess > promotionCutoff) { actions.push({ action: 'promote', section: section.title, from: tier, ...[truncated 3562 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove filename inference from arbitrary tool-result text. Do not treat a textual filename mention as proof of file access. 2. Accept access events only from authenticated, structured `read`, `memory_get`, or `memory_search` records with verifiable tool identity and arguments. 3. Record exact file and section provenance for search results rather than deriving it with a broad regular expression. 4. Do not use file-level timestamps as a fallback when deciding whether to promote individual sections. 5. Require a recent, exact section-level access event before automatic promotion. 6. Validate timestamps and reject malformed, future-dated, or implausibly old transcript events. 7. Add an approval step before lower-tier content is written into `MEMORY.md`, particularly when the content originated from untrusted sources. 8. Apply limits to the number of sections and total bytes promoted in one maintenance run. 9. Preserve and display the provenance of every proposed promotion in dry-run output. 10. Add regression tests in which arbitrary tool results mention tier filenames and verify that no access or promotion is recorded. ]]>
