langchain-ai/deepagents · error · ValueError

Path traversal detected after normalization: {path} -> {norm

Error message

Path traversal detected after normalization: {path} -> {normalized}

What it means

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.

Source

Thrown at libs/deepagents/deepagents/backends/utils.py:718

    if ".." in parts or path.startswith("~"):
        msg = f"Path traversal not allowed: {path}"
        raise ValueError(msg)

    # Reject Windows absolute paths (e.g., C:\..., D:/...)
    if re.match(r"^[a-zA-Z]:", path):
        msg = f"Windows absolute paths are not supported: {path}. Please use virtual paths starting with / (e.g., /workspace/file.txt)"
        raise ValueError(msg)

    normalized = os.path.normpath(path)
    normalized = normalized.replace("\\", "/")

    if not normalized.startswith("/"):
        normalized = f"/{normalized}"

    # Defense-in-depth: verify normpath didn't produce traversal
    if ".." in normalized.split("/"):
        msg = f"Path traversal detected after normalization: {path} -> {normalized}"
        raise ValueError(msg)

    if allowed_prefixes is not None and not any(normalized.startswith(prefix) for prefix in allowed_prefixes):
        msg = f"Path must start with one of {allowed_prefixes}: {path}"
        raise ValueError(msg)

    return normalized


def _normalize_path(path: str | None) -> str:
    """Normalize a path to canonical form.

    Converts path to absolute form starting with /, removes trailing slashes
    (except for root), and validates that the path is not empty.

    Args:
        path: Path to normalize (None defaults to "/")

    Returns:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Remove or resolve '..' segments before calling (posixpath.normpath on an absolute path).
  2. Ensure the path is absolute and rooted inside an allowed prefix (e.g. the workspace root).
  3. If the traversal is intentional, expand it yourself to the real absolute path and pass that.
  4. Wrap the call in try/except ValueError and surface a user-facing 'invalid path' message.

Example fix

// before
backend.read("../../etc/passwd")
// after
import posixpath
safe = posixpath.normpath(posixpath.join("/workspace", user_path))
if safe.startswith("/workspace"):
    backend.read(safe)
Defensive patterns

Strategy: validation

Validate before calling

import posixpath
def is_safe_path(user_path: str, root: str = "/workspace") -> bool:
    absolute = posixpath.normpath(posixpath.join(root, user_path))
    return absolute.startswith(root + "/") or absolute == root

Type guard

def is_normalized_safe(normalized: str) -> bool:
    return normalized.startswith("/") and ".." not in normalized.split("/")

Try / catch

try:
    backend.read(path)
except ValueError as exc:
    if "traversal" in str(exc):
        return {"error": f"Refusing unsafe path: {path}"}
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/a6a261019a1f8d07. Report an issue: GitHub.