T02 · Agent Memory Poisoning
- Location
scripts/send-mirrored.sh:182- Finding
Ambiguous Destination Matching Can Poison an Unrelated Agent Session
- Content
View full analysis
Vulnerability Details
File Location:
scripts/send-mirrored.sh, lines 182-191 and 263-307
Vulnerability Type: Persistent cross-session transcript poisoning through ambiguous substring matching
Risk Level: MediumVulnerable Code
python # fall back: match by delivery target + thread across all entries if entry is None: for _k, v in sessions.items(): if not isinstance(v, dict): continue dc = v.get("deliveryContext") or {} rt = (v.get("route") or {}).get("target") or {} dest = dc.get("to") or rt.get("to") or "" tid = dc.get("threadId") if tid is None: tid = (v.get("route") or {}).get("thread", {}).get("id") if str(to) in dest and (str(tid) == str(thread) if thread else tid in (None, "")): entry = v; breakThe selected session then receives caller-controlled content as an assistant-authored record:
python record = { "type": "message", "id": str(uuid.uuid4()), "parentId": parent_id, "timestamp": iso, "message": { "role": "assistant", "content": [{"type": "text", "text": msg}], "api": "openai-responses", "provider": "openclaw", "model": "delivery-mirror", "usage": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0, "totalTokens": 0, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0, "total": 0, }, }, "stopReason": "stop", "timestamp": now_ms, "openclawDeliveryMirror": marker, }, } line = ("\n" if needs_newline else "") + json.dumps(record, ensure_ascii=False) + "\n" with open(session_file, "a", encoding="utf-8") as f: f.write(line)...[truncated 2997 chars]
- Remediation
View remediation
Remediation Suggestions
-
Replace substring containment with exact comparison after canonicalizing both identifiers to a documented representation:
python if str(to).strip() == str(dest).strip() and ( str(tid) == str(thread) if thread else tid in (None, "") ): matches.append(v) -
Collect all fallback matches rather than accepting the first one. Continue only when exactly one session matches; otherwise emit a warning and skip mirroring:
python if len(matches) != 1: warn(f"expected one exact session match, found {len(matches)}; mirror skipped") sys.exit(0) entry = matches[0] -
Prefer explicit
--session-keyresolution when deterministic generated keys fail. For security-sensitive deployments, disable fallback scanning entirely. -
Validate that the resolved session key and session metadata consistently match the requested agent, channel, destination, and thread before opening
sessionFile. -
Canonicalize and type-check
deliveryContext.to,route.target.to, and thread identifiers. Reject dictionaries, lists, malformed identifiers, or unexpected schemes rather than coercing arbitrary values. -
Add regression tests covering destination collisions such as
123versus1234, prefixed channel identifiers, multiple exact matches, thread mismatches, and malformed session metadata. -
Consider adding explicit provenance indicating that the transcript text originated from an external script. This reduces the chance that future agent runs interpret caller-controlled content as independently generated assistant instructions.
-
