{"record":{"id":"f2cb226391a24cc5","repo":"langchain-ai/deepagents","slug":"repository-root-must-be-an-absolute-contained-path","errorCode":null,"errorMessage":"Repository root must be an absolute contained path: {root!r}","messagePattern":"Repository root must be an absolute contained path: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/_repository_bounds.py","lineNumber":83,"sourceCode":"\nclass RepositoryBounds:\n    \"\"\"Path-safety and size limits for read-only repository inspection tools.\"\"\"\n\n    def __init__(self, backend: BackendProtocol, *, root: str = \"/\") -> None:\n        \"\"\"Initialize repository bounds rooted at an absolute backend path.\n\n        Args:\n            backend: Server-side repository backend used by filesystem tools.\n            root: Absolute backend path that bounds repository reads.\n\n        Raises:\n            ValueError: If `root` is not a safe absolute path.\n        \"\"\"\n        normalized = root.replace(\"\\\\\", \"/\")\n        path = PurePosixPath(normalized)\n        if not normalized.startswith(\"/\") or \"..\" in path.parts or \"~\" in root:\n            msg = f\"Repository root must be an absolute contained path: {root!r}\"\n            raise ValueError(msg)\n        self._backend = backend\n        self._root = str(path)\n        self._sandbox = backend if isinstance(backend, SandboxBackendProtocol) else None\n        self._filesystem = (\n            backend\n            if self._sandbox is None and isinstance(backend, FilesystemBackend)\n            else None\n        )\n        self._filesystem_root: Path | None = None\n        if self._filesystem is not None:\n            try:\n                self._filesystem_root = self._resolve_filesystem_path(self._root)\n            except _BACKEND_ERRORS:\n                logger.warning(\n                    \"Could not resolve the local repository root; local repository \"\n                    \"paths will be unavailable\",\n                    exc_info=True,\n                )","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/_repository_bounds.py#L65-L101","documentation":"`RepositoryBounds.__init__` raises this `ValueError` when the `root` argument passed to it is not a safe absolute path. The root bounds all read-only repository inspection tools (ls, read_file, glob, grep), so it must be absolute (`startswith(\"/\")`), must not contain `..` components, and must not contain `~`. Anything else could let path traversal escape the bounded sandbox, so construction fails fast.","triggerScenarios":"Calling `RepositoryBounds(backend, root=\"repo\")`, `root=\"~/repo\"`, `root=\"/a/../b\"`, or `root=\"C:\\\\repo\"` (backslashes are normalized but the drive is not a leading `/`). Also triggered when a computed root from user config or CLI output is passed through unvalidated.","commonSituations":"Passing a Windows-style path on a POSIX-normalizing API; building the root by string concatenation without `os.path.abspath`; forwarding a user-supplied workspace path that contains `..` or `~`; using a relative default in a config file consumed by a server component that constructs `RepositoryBounds`.","solutions":["Pass an absolute POSIX-style root: `RepositoryBounds(backend, root=\"/abs/path/to/repo\")`.","Resolve relative inputs first: `root = str(Path(user_root).expanduser().resolve())` before constructing.","Remove or collapse `..` components with `Path.resolve()` or `os.path.normpath` plus absolutization.","Convert Windows paths (drive letters, backslashes) to absolute POSIX paths appropriate for the backend environment."],"exampleFix":"// before\nbounds = RepositoryBounds(backend, root=\"~/project\")\n// after\nfrom pathlib import Path\nbounds = RepositoryBounds(backend, root=str(Path(\"~/project\").expanduser().resolve()))","handlingStrategy":"validation","validationCode":"from pathlib import PurePosixPath\n\ndef safe_repository_root(root: str) -> bool:\n    normalized = root.replace(\"\\\\\", \"/\")\n    path = PurePosixPath(normalized)\n    return (\n        normalized.startswith(\"/\")\n        and \"..\" not in path.parts\n        and \"~\" not in root\n    )","typeGuard":"def is_absolute_contained(path: str) -> bool:\n    p = PurePosixPath(path.replace(\"\\\\\", \"/\"))\n    return path.startswith(\"/\") and \"..\" not in p.parts and \"~\" not in path","tryCatchPattern":"try:\n    bounds = RepositoryBounds(backend, root=resolved_root)\nexcept ValueError as exc:\n    raise ConfigError(f\"repository root rejected: {exc}\") from exc","preventionTips":["Normalize with Path(...).expanduser().resolve() before passing any root.","Never build roots by raw string concatenation; use pathlib joins.","Convert Windows paths to the backend's expected absolute form first.","Treat '~'-containing roots as user input needing expansion, never pass through."],"tags":["path-validation","security","configuration"],"backgroundTag":"unsafe-path-traversal","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}