T02 · Agent Memory Poisoning
- Location
app.py:204- Finding
Persistent System-Prompt Injection Through Stored Memories and Notes
- Content
View full analysis
Vulnerability Details
File Location:
app.py:204-257,app.py:496-516,memory.py:220-268
Vulnerability Type: Persistent prompt injection through unsanitized stored context
Risk Level: MediumVulnerable Code
python def build_memory_context(phone_hash: str) -> str: """Assemble a memory context block for injection into the system prompt. Combines long-term facts and relevant daily notes for the caller. """ sections: list[str] = [] facts = get_memories(phone_hash) if facts: bullet_list = "\n".join(f"- {f}" for f in facts) sections.append(f"## Known Facts About This Caller\n{bullet_list}") notes = get_notes(phone_hash) if notes: note_lines = "\n".join( f"- [{datetime.fromtimestamp(n['timestamp'], tz=timezone.utc).strftime('%Y-%m-%d')}] {n['note']}" for n in notes[-10:] ) sections.append(f"## Context Notes\n{note_lines}") return "\n\n".join(sections) def build_system_prompt(session: Session, phone_hash: str) -> str: """Build the full system prompt from soul template + memory context.""" parts: list[str] = [_soul_template] memory_ctx = build_memory_context(phone_hash) caller_name = session.get("caller_name", "") call_count = session.get("call_count", 1) caller_info_lines: list[str] = [] if caller_name: caller_info_lines.append(f"- Caller name: {caller_name}") caller_info_lines.append(f"- This is call #{call_count} from this caller") if call_count > 1: caller_info_lines.append("- This is a returning caller — reference prior context naturally") caller_section = "## Caller Info\n" + "\n".join(caller_info_lines) context_block = "\n\n".join(filter(None, [caller_section, memory_ctx])) parts.append(f"---\n# Caller Context\n{context_block}") return "\n\n".join(parts)Administrative endpoin ...[truncated 3583 chars]
- Remediation
View remediation
Remediation Suggestions
- Treat all facts and notes as untrusted data, even when submitted through an authenticated administrative endpoint.
- Represent context using a structured data block rather than unrestricted prose whenever the downstream API permits it.
- Add an explicit higher-priority instruction stating that memory and note content is reference data, cannot modify policies, and must never be interpreted as instructions.
- Place stored content inside clearly delimited or serialized fields and escape delimiter characters.
- Apply strict maximum lengths and collection limits to facts, notes, and the final generated prompt.
- Validate content and reject known instruction-injection patterns where appropriate, while recognizing that filtering alone is not a complete defense.
- Restrict global-note creation to a separate, more privileged operation because global entries affect all callers.
- Use separate credentials and narrowly scoped authorization for caller-specific and global writes.
- Maintain audit logs for memory creation, modification, deletion, submitting identity, and affected caller scope.
- Add deletion and review workflows so poisoned entries can be identified and removed promptly.
- Ensure downstream tools independently enforce authorization and never rely on prompt instructions as a security boundary.
