T02 · Agent Memory Poisoning
- Location
memory_system.py:86- Finding
Persistent LLM Prompt Injection Through Constitutional Memory
- Content
View full analysis
Vulnerability Details
File Location:
memory_system.py:86-112, 267-306; related input and usage guidance atcli.py:66-79, 119-127andSKILL.md:77-79, 136-140
Vulnerability Type: Persistent agent-memory poisoning leading to prompt injection
Risk Level: HighComplete Code Snippet
The CLI accepts arbitrary lesson content:
python add.add_argument("--strategy", required=True, help="Strategy identifier") add.add_argument( "--category", required=True, help="Category (e.g. drawdown, selection, position_sizing, timing, ml_failure, success)", ) add.add_argument("--description", required=True, help="Root-cause description") add.add_argument("--evidence", default="", help="Supporting data / evidence")The content is persisted without trust validation:
python lesson = { "id": len(self.lessons) + 1, "timestamp": datetime.now().isoformat(), "strategy": strategy_name, "category": category, "description": description, "evidence": evidence, "severity": severity, } self.lessons.append(lesson) if new_ban and new_ban not in self.bans: self.bans.append(new_ban) self._save()The stored values are subsequently interpolated verbatim into LLM context:
python for lesson in sorted_lessons: label = SEVERITY_LABELS.get(lesson["severity"], "[INFO]") lines.append( f"{label} [{lesson['strategy']}] {lesson['category']}: " f"{lesson['description']}" ) if lesson.get("evidence"): lines.append(f" Evidence: {lesson['evidence']}") lines.append(f"\n=== Banned Code Patterns ({len(self.bans)} total) ===") for ban in self.bans: lines.append(f" X Prohibited: `{ban}`") return "\n".join(lines)The documentation instructs consumers to use this generated text as prompt material:
markdown context = memory.get_context(max_lessons=30) # Returns formatted text with lessons sorted by severity, # banned patterns list, verified blueprints, and core rulesmarkd ...[truncated 2823 chars]- Remediation
View remediation
Remediation Suggestions
-
Separate policy from observations
- Store administrator-approved constitutional rules separately from user-supplied lessons and diagnostic evidence.
- Never promote untrusted observations into authoritative prompt instructions automatically.
-
Use structured, explicitly untrusted context
- Supply lessons as structured data rather than concatenating them into the system prompt.
- Delimit the data and add a higher-priority instruction stating that stored entries are historical records, not executable instructions.
- Ensure downstream prompt templates do not characterize unreviewed content as mandatory policy.
-
Add validation and normalization
- Enforce allowlists for categories and severity values in both the API and CLI.
- Apply reasonable length limits to all fields.
- Detect or quarantine instruction-like content, prompt-role markers, and attempts to override prior rules.
- Validate JSON types and schemas when loading persisted files.
-
Introduce provenance and approval controls
- Record the source and author of each lesson.
- Require trusted review before a lesson or ban becomes prompt-visible.
- Consider signing approved memory records and rejecting records with invalid signatures.
-
Protect memory integrity
- Restrict filesystem permissions on the memory directory.
- Use atomic writes and integrity checks.
- Provide quarantine, rollback, and deletion mechanisms for poisoned entries, notwithstanding the current append-only guidance.
-
Apply least privilege downstream
- Require independent policy enforcement before executing code or actions generated by the LLM.
- Do not rely on prompt instructions alone to enforce prohibited operations.
-
