T02 · Agent Memory Poisoning
Error
- Location
- scripts/sync_discord_identity.py:91
- Finding
- Persistent Agent Identity Poisoning Through Unescaped Discord Profile Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync_discord_identity.py`, lines 91–100 and 209 **Vulnerability Type**: T02: Agent Memory Poisoning **Risk Level**: High ### Vulnerable Code ```python def ensure_discord_block_lines(data: Dict[str, Any]) -> List[str]: lines: List[str] = [] for key in ("username", "locale", "email", "bio"): value = data.get(key) if value is None: continue if isinstance(value, str) and not value.strip(): continue lines.append(f" - {key}: {value}") return ["- **Discord:**", *lines] if lines else [] ``` The resulting lines are written into the persistent identity file: ```python lines = upsert_discord_block(lines, ensure_discord_block_lines(profile)) ``` ### Technical Analysis The Discord API response is external, potentially attacker-controlled input. Fields such as `username`, `email`, and particularly `bio` are inserted directly into `IDENTITY.md` without: - Removing carriage returns or newline characters - Escaping Markdown syntax - Restricting values to a single line - Enforcing field-specific formats - Applying length limits - Requiring confirmation before persistent storage A multiline profile value can escape the intended nested bullet and introduce new Markdown sections, identity attributes, or instruction-like content. Because `IDENTITY.md` is persistent agent identity or state content, injected text may be loaded into future agent sessions and interpreted as trusted context. The vulnerability does not require the Skill itself to contain malicious instructions. It creates a data flow from an externally controlled Discord profile into persistent agent state without a sufficient trust-boundary check. ### Attack Path 1. An attacker gains the ability to edit the selected Discord bot profile, or compromises an account with that ability. 2. The attacker places multiline Markdown or instruction-like content in a synchronized field, most pl ...[truncated 1235 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Treat every Discord profile field as untrusted external data. 2. Reject values containing `\r`, `\n`, null bytes, or other control characters before constructing Markdown. 3. Apply strict, field-specific validation: - Validate `locale` against a conservative locale pattern or allowlist. - Validate `email` with a bounded single-line format. - Restrict `username` to a safe length and single line. - Exclude `bio` by default because it is free-form content. 4. If biography synchronization is required, make it an explicit opt-in option and require a user-reviewed diff before writing. 5. Enforce conservative maximum lengths for every stored field. 6. Escape Markdown metacharacters or store synchronized metadata in a structured, inert format that is not interpreted as agent instructions. 7. Build the Discord block from validated scalar values only; do not interpolate arbitrary objects or multiline strings. 8. Consider recording the data under a clearly delimited “external metadata” section that the agent is instructed not to interpret as operational instructions. 9. Write changes atomically only after all external fields pass validation. Example defensive validation: ```python def safe_single_line(value: Any, max_length: int) -> str: if not isinstance(value, str): raise ValueError("Expected a string value") if any(ch in value for ch in ("\r", "\n", "\x00")): raise ValueError("Multiline or control-character content is not allowed") value = value.strip() if len(value) > max_length: raise ValueError("Profile field exceeds the permitted length") return value ``` ]]>
