{"record":{"id":"87977ab8fbb779fb","repo":"Graphify-Labs/graphify","slug":"path-path-r-escapes-the-allowed-directory-base","errorCode":null,"errorMessage":"Path {path!r} escapes the allowed directory {base}. Only paths inside graphify-out/ are permitted.","messagePattern":"Path (.+?) escapes the allowed directory (.+?)\\. Only paths inside graphify-out/ are permitted\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"graphify/security.py","lineNumber":346,"sourceCode":"        for candidate in [resolved_hint, *resolved_hint.parents]:\n            if candidate.name == GRAPHIFY_OUT_NAME:\n                base = candidate\n                break\n        if base is None:\n            base = Path(GRAPHIFY_OUT).resolve()\n\n    base = base.resolve()\n    if not base.exists():\n        raise ValueError(\n            f\"Graph base directory does not exist: {base}. \"\n            \"Run /graphify first to build the graph.\"\n        )\n\n    resolved = Path(path).resolve()\n    try:\n        resolved.relative_to(base)\n    except ValueError:\n        raise ValueError(\n            f\"Path {path!r} escapes the allowed directory {base}. \"\n            \"Only paths inside graphify-out/ are permitted.\"\n        )\n\n    if not resolved.exists():\n        raise FileNotFoundError(f\"Graph file not found: {resolved}\")\n\n    return resolved\n\n\ndef check_graph_file_size_cap(path: Path) -> None:\n    \"\"\"Reject *path* if its size exceeds the configured graph-file cap.\n\n    Protects callers from memory bombs by failing fast before a multi-GiB\n    graph.json is read into memory and JSON-parsed. Silently returns when\n    ``path.stat()`` cannot be read — the caller's own existence/path check\n    is expected to surface a clearer error in that case.\n","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/security.py#L328-L364","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Only request files that actually live under the graphify-out/ base directory.","Construct paths with (base / relative_name) instead of concatenating strings, so traversal cannot occur.","Remove or retarget symlinks inside graphify-out/ that resolve outside the base.","If you are a server operator seeing this from clients, treat it as a malformed request, not something to work around."],"exampleFix":"# before\np = resolve_graph_path(\"graphify-out/../../secrets/config.json\")\n\n# after: stay inside the base\np = resolve_graph_path(\"graphify-out/wiki/index.md\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef safe_graph_path(base: Path, rel: str | Path) -> Path:\n    \"\"\"Compose base + rel and verify containment before calling the library.\"\"\"\n    candidate = (base / rel).resolve()\n    if base.resolve() not in candidate.parents and candidate != base.resolve():\n        raise ValueError(f\"{rel!r} escapes {base}\")\n    return candidate","typeGuard":"def is_within_base(path: str | Path, base: Path) -> bool:\n    try:\n        Path(path).resolve().relative_to(base.resolve())\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    p = resolve_graph_path(user_path)\nexcept ValueError as e:\n    if \"escapes the allowed directory\" in str(e):\n        # client-supplied path — reject, never broaden\n        return error_response(\"path outside graphify-out\")\n    raise","preventionTips":["Build paths with Path(base) / relative_part, never string concatenation.","Reject or normalize '../' segments in any client-supplied path.","Audit symlinks under graphify-out/ so resolve() stays inside the base."],"tags":["security","filesystem","path-traversal","graph"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}