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

  1. 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').
  2. If you cannot establish provenance, treat the record as hostile: remove it and re-create the fact, which regenerates a safe relative path.
  3. 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

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


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/438e0ff491a40afc. Report an issue: GitHub.