bytedance/deer-flow · critical · MemoryStorageCorruption
{label} path escapes the user memory directory: {relative!r}
Error message
{label} path escapes the user memory directory: {relative!r} What it means
A persisted relative path (e.g. a fact's Markdown object path) was resolved against the memory root and turned out to be absolute, so _safe_relative_path raises MemoryStorageCorruption. Persisted paths are untrusted: an absolute path would let storage read or write outside the user's memory directory, so it is treated as corruption/attack data, not normalized away.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:260
existing_material = {key: value for key, value in existing.items() if key not in comparison_keys}
if incoming_material == existing_material:
normalized["revision"] = existing_revision
normalized["updatedAt"] = existing.get("updatedAt") or normalized["createdAt"]
else:
normalized["revision"] = existing_revision + 1
normalized["updatedAt"] = now
if not isinstance(normalized.get("createdAt"), str) or not isinstance(normalized.get("updatedAt"), str):
raise ValueError("fact.createdAt and fact.updatedAt must be strings")
if normalized["consolidatedFrom"]:
normalized.setdefault("consolidatedAt", normalized["updatedAt"])
return normalized
def _safe_relative_path(root: Path, relative: str, *, label: str) -> Path:
"""Resolve an untrusted persisted relative path without leaving root."""
candidate = Path(relative)
if candidate.is_absolute():
raise MemoryStorageCorruption(f"{label} path escapes the user memory directory: {relative!r}")
root_resolved = root.resolve()
resolved = (root / candidate).resolve()
try:
resolved.relative_to(root_resolved)
except ValueError as exc:
raise MemoryStorageCorruption(f"{label} path escapes the user memory directory: {relative!r}") from exc
return resolved
def _fact_title(fact: dict[str, Any]) -> str:
explicit = str(fact.get("title") or "").strip()
if explicit:
return explicit.replace("\n", " ")[:160]
first = str(fact.get("content") or "Memory fact").splitlines()[0].strip()
return (first or "Memory fact")[:160]
def _render_fact_markdown(fact: dict[str, Any]) -> bytes:View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Inspect the record named in the message and rewrite the stored path as relative to the user's memory root (e.g. 'facts/abc.md').
- If you cannot establish provenance, treat the record as hostile: remove it and re-create the fact, which regenerates a safe relative path.
- Prevent recurrence by never writing absolute paths into memory records; log and reject them at import time.
Example fix
# before (stored record): {"markdown": "/home/old-user/memory/facts/abc.md"}
# after (stored record): {"markdown": "facts/abc.md"} Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PurePosixPath
def safe_stored_path(raw: str) -> bool:
p = PurePosixPath(raw)
return not p.is_absolute() and ".." not in p.parts Type guard
from pathlib import PurePosixPath
def is_relative_within_root(raw: object) -> bool:
if not isinstance(raw, str):
return False
p = PurePosixPath(raw)
return not p.is_absolute() and ".." not in p.parts Try / catch
try:
store.load(record)
except MemoryStorageCorruption as exc:
if "escapes the user memory directory" in str(exc):
quarantine(record) # never auto-resolve absolute paths
raise Prevention
- Only ever persist plain relative paths (e.g. 'facts/abc.md') in memory records.
- Reject absolute paths and '..' segments at import time as untrusted input.
- Never let external tooling rewrite stored paths to absolute forms.
When it happens
Trigger: A fact record whose markdown path field is '/etc/passwd' or '/var/lib/data.json' - from a malicious import, a hand-crafted store file, or a producer that stored absolute paths from a different machine's layout.
Common situations: Migrating stores between machines where absolute layouts differ and someone 'fixed' paths absolutely; untrusted skill/extension-supplied memory files; the directory moved after paths were recorded absolutely.
Related errors
- Legacy {section} summary migration conflict in {legacy_path}
- Stored fact {normalized['id']!r} has an invalid revision
- Invalid path
- Missing or empty 'messages' key in {path}
- chat prompt template not found: {name} (searched: {searched}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/438e0ff491a40afc.
Report an issue: GitHub.