Graphify-Labs/graphify · error · ValueError

Path {path!r} escapes the allowed directory {base}. Only pat

Error message

Path {path!r} escapes the allowed directory {base}. Only paths inside graphify-out/ are permitted.

What it means

Raised by the graph-path validator in graphify/security.py when the resolved absolute path of a requested graph file does not lie under the allowed graphify-out/ base directory (resolved.relative_to(base) fails). It is a path-traversal guard: only files inside the graph output directory may be loaded through this API.

Source

Thrown at graphify/security.py:346

        for candidate in [resolved_hint, *resolved_hint.parents]:
            if candidate.name == GRAPHIFY_OUT_NAME:
                base = candidate
                break
        if base is None:
            base = Path(GRAPHIFY_OUT).resolve()

    base = base.resolve()
    if not base.exists():
        raise ValueError(
            f"Graph base directory does not exist: {base}. "
            "Run /graphify first to build the graph."
        )

    resolved = Path(path).resolve()
    try:
        resolved.relative_to(base)
    except ValueError:
        raise ValueError(
            f"Path {path!r} escapes the allowed directory {base}. "
            "Only paths inside graphify-out/ are permitted."
        )

    if not resolved.exists():
        raise FileNotFoundError(f"Graph file not found: {resolved}")

    return resolved


def check_graph_file_size_cap(path: Path) -> None:
    """Reject *path* if its size exceeds the configured graph-file cap.

    Protects callers from memory bombs by failing fast before a multi-GiB
    graph.json is read into memory and JSON-parsed. Silently returns when
    ``path.stat()`` cannot be read — the caller's own existence/path check
    is expected to surface a clearer error in that case.

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. Only request files that actually live under the graphify-out/ base directory.
  2. Construct paths with (base / relative_name) instead of concatenating strings, so traversal cannot occur.
  3. Remove or retarget symlinks inside graphify-out/ that resolve outside the base.
  4. If you are a server operator seeing this from clients, treat it as a malformed request, not something to work around.

Example fix

# before
p = resolve_graph_path("graphify-out/../../secrets/config.json")

# after: stay inside the base
p = resolve_graph_path("graphify-out/wiki/index.md")
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def safe_graph_path(base: Path, rel: str | Path) -> Path:
    """Compose base + rel and verify containment before calling the library."""
    candidate = (base / rel).resolve()
    if base.resolve() not in candidate.parents and candidate != base.resolve():
        raise ValueError(f"{rel!r} escapes {base}")
    return candidate

Type guard

def is_within_base(path: str | Path, base: Path) -> bool:
    try:
        Path(path).resolve().relative_to(base.resolve())
        return True
    except ValueError:
        return False

Try / catch

try:
    p = resolve_graph_path(user_path)
except ValueError as e:
    if "escapes the allowed directory" in str(e):
        # client-supplied path — reject, never broaden
        return error_response("path outside graphify-out")
    raise

Prevention

When it happens

Trigger: Passing a path containing ../ sequences that escape graphify-out/, an absolute path pointing elsewhere on disk (/etc/passwd style probes), or a symlink inside graphify-out whose target resolves outside the base (Path.resolve() follows symlinks before the check).

Common situations: MCP client or tool consumer sends a project_path/file path outside the graph directory; scripts accidentally passing the repo root or a source file instead of the graph JSON; symlinks in graphify-out pointing at shared storage outside the tree.

Related errors


AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14). Data as JSON: /api/errors/87977ab8fbb779fb. Report an issue: GitHub.