{"record":{"id":"a6a261019a1f8d07","repo":"langchain-ai/deepagents","slug":"path-traversal-detected-after-normalization-path","errorCode":null,"errorMessage":"Path traversal detected after normalization: {path} -> {normalized}","messagePattern":"Path traversal detected after normalization: (.+?) -> (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/deepagents/deepagents/backends/utils.py","lineNumber":718,"sourceCode":"    if \"..\" in parts or path.startswith(\"~\"):\n        msg = f\"Path traversal not allowed: {path}\"\n        raise ValueError(msg)\n\n    # Reject Windows absolute paths (e.g., C:\\..., D:/...)\n    if re.match(r\"^[a-zA-Z]:\", path):\n        msg = f\"Windows absolute paths are not supported: {path}. Please use virtual paths starting with / (e.g., /workspace/file.txt)\"\n        raise ValueError(msg)\n\n    normalized = os.path.normpath(path)\n    normalized = normalized.replace(\"\\\\\", \"/\")\n\n    if not normalized.startswith(\"/\"):\n        normalized = f\"/{normalized}\"\n\n    # Defense-in-depth: verify normpath didn't produce traversal\n    if \"..\" in normalized.split(\"/\"):\n        msg = f\"Path traversal detected after normalization: {path} -> {normalized}\"\n        raise ValueError(msg)\n\n    if allowed_prefixes is not None and not any(normalized.startswith(prefix) for prefix in allowed_prefixes):\n        msg = f\"Path must start with one of {allowed_prefixes}: {path}\"\n        raise ValueError(msg)\n\n    return normalized\n\n\ndef _normalize_path(path: str | None) -> str:\n    \"\"\"Normalize a path to canonical form.\n\n    Converts path to absolute form starting with /, removes trailing slashes\n    (except for root), and validates that the path is not empty.\n\n    Args:\n        path: Path to normalize (None defaults to \"/\")\n\n    Returns:","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/deepagents/deepagents/backends/utils.py#L700-L736","documentation":"validate_path performs defense-in-depth path-traversal detection on a normalized path. If, after POSIX normalization (resolving '..' segments via normpath), any path component is still '..', the input could not be safely canonicalized, so a ValueError is raised to prevent reads/writes outside the intended root.","triggerScenarios":"Calling any backend file operation (ls, read, write, glob, grep) whose path argument, after normalization, still contains a '..' segment — e.g. validate_path('a/../../etc/passwd') when normalization cannot resolve the traversal, or paths built by joining user input with '..' fragments.","commonSituations":"Passing user/agent-supplied paths directly into backend operations; composing relative segments without sanitizing; sandboxed setups expecting paths under a workspace root but receiving escape paths.","solutions":["Remove or resolve '..' segments before calling (posixpath.normpath on an absolute path).","Ensure the path is absolute and rooted inside an allowed prefix (e.g. the workspace root).","If the traversal is intentional, expand it yourself to the real absolute path and pass that.","Wrap the call in try/except ValueError and surface a user-facing 'invalid path' message."],"exampleFix":"// before\nbackend.read(\"../../etc/passwd\")\n// after\nimport posixpath\nsafe = posixpath.normpath(posixpath.join(\"/workspace\", user_path))\nif safe.startswith(\"/workspace\"):\n    backend.read(safe)","handlingStrategy":"validation","validationCode":"import posixpath\ndef is_safe_path(user_path: str, root: str = \"/workspace\") -> bool:\n    absolute = posixpath.normpath(posixpath.join(root, user_path))\n    return absolute.startswith(root + \"/\") or absolute == root","typeGuard":"def is_normalized_safe(normalized: str) -> bool:\n    return normalized.startswith(\"/\") and \"..\" not in normalized.split(\"/\")","tryCatchPattern":"try:\n    backend.read(path)\nexcept ValueError as exc:\n    if \"traversal\" in str(exc):\n        return {\"error\": f\"Refusing unsafe path: {path}\"}\n    raise","preventionTips":["Always normalize user-supplied paths with posixpath.normpath before passing to backend operations.","Join user input onto a fixed root instead of accepting absolute paths.","Keep '..' out of any path composed from external input.","Add allowlist-prefix checks in your own tool wrappers before calling the backend."],"tags":["path-traversal","security","validation"],"backgroundTag":"path-traversal-detected","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}