{"record":{"id":"87072a1a66a96b6b","repo":"langchain-ai/deepagents","slug":"path-full-outside-root-directory-self-cwd","errorCode":null,"errorMessage":"Path:{full} outside root directory: {self.cwd}","messagePattern":"Path:(.+?) outside root directory: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/deepagents/deepagents/backends/filesystem.py","lineNumber":213,"sourceCode":"        Returns:\n            Resolved absolute `Path` object.\n\n        Raises:\n            ValueError: If path traversal is attempted in `virtual_mode` or if the\n                resolved path escapes the root directory.\n            OSError: If the path is a symlink loop (`ELOOP`).\n        \"\"\"\n        if self.virtual_mode:\n            vpath = key if key.startswith(\"/\") else \"/\" + key\n            if \"..\" in vpath or vpath.startswith(\"~\"):\n                msg = \"Path traversal not allowed\"\n                raise ValueError(msg)\n            full = (self.cwd / vpath.lstrip(\"/\")).resolve()\n            try:\n                full.relative_to(self.cwd)\n            except ValueError:\n                msg = f\"Path:{full} outside root directory: {self.cwd}\"\n                raise ValueError(msg) from None\n            _raise_if_symlink_loop(full)\n            return full\n\n        path = Path(key)\n        if path.is_absolute():\n            _raise_if_symlink_loop(path)\n            return path\n        resolved = (self.cwd / path).resolve()\n        _raise_if_symlink_loop(resolved)\n        return resolved\n\n    def _to_virtual_path(self, path: Path) -> str:\n        \"\"\"Convert a filesystem path to a virtual path relative to cwd.\n\n        Args:\n            path: Filesystem path to convert.\n\n        Returns:","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/deepagents/deepagents/backends/filesystem.py#L195-L231","documentation":"After resolving a virtual-mode path, the backend verifies the fully resolved (symlink-followed) path sits under the backend root via relative_to. If the resolved path escapes the root — even when the textual path looked safe — ValueError is raised. This catches symlinks that point outside the sandbox.","triggerScenarios":"Calling ls/read/write/edit/delete/grep with a path (or a symlink target) that resolves outside self.cwd, e.g. 'link' pointing to /etc, or a deeply nested '../../' chain that survives normalization.","commonSituations":"Symlinks created inside the workspace that point to external files, or mounting a workspace where cwd differs from expectations (running from a different working directory).","solutions":["Inspect the resolved path printed in the message and use one contained within the root directory","Remove or re-point symlinks that escape the backend root","Verify the backend was constructed with the intended root_dir / cwd","Copy external files into the root rather than linking to them"],"exampleFix":"// before\nbackend.read(\"shared-link/data.txt\")  # symlink -> /etc/data.txt\n// after\ncp /etc/data.txt ./data.txt\nbackend.read(\"data.txt\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef stays_in_root(root: Path, key: str) -> bool:\n    resolved = (root / key.lstrip(\"/\")).resolve()\n    return resolved == root or root in resolved.parents","typeGuard":null,"tryCatchPattern":"try:\n    data = backend.read(key)\nexcept ValueError as exc:\n    if str(exc).startswith(\"Path:\") and \"outside root directory\" in str(exc):\n        data = None  # symlink or path escapes sandbox; handle explicitly\n    else:\n        raise","preventionTips":["Audit symlinks inside the workspace for external targets","Copy external files into the backend root instead of linking","Confirm root_dir matches your intended sandbox when constructing the backend"],"tags":["python","path-traversal","symlink","sandbox","filesystem"],"backgroundTag":"path-escape-root","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}