BerriAI/litellm · warning · ValueError

Invalid file path {file_path!r}: path traversal detected

Error message

Invalid file path {file_path!r}: path traversal detected

What it means

The second branch of _sanitize_file_path: after splitting the path on '/', any segment equal to '..' triggers a path-traversal rejection. This prevents crafted paths from escaping the intended repository directory in the constructed BitBucket API URL. The check runs on the literal segment value before URL-encoding, so an encoded '%2E%2E' that later decodes to '..' upstream would still need to be literal here to be caught — the guard is on the raw input.

Source

Thrown at litellm/integrations/bitbucket/bitbucket_client.py:19

"""
BitBucket API client for fetching .prompt files from BitBucket repositories.
"""

import base64
import urllib.parse
from typing import Any, Final

from litellm.llms.custom_httpx.http_handler import HTTPHandler


def _sanitize_file_path(file_path: str) -> str:
    """Reject path traversal and URL-encode each path segment."""
    if "#" in file_path or "?" in file_path:
        raise ValueError(f"Invalid file path {file_path!r}: contains URL special characters")
    parts: Final = file_path.split("/")
    for part in parts:
        if part == "..":
            raise ValueError(f"Invalid file path {file_path!r}: path traversal detected")
    return "/".join(urllib.parse.quote(part, safe="") for part in parts)


class BitBucketClient:
    """
    Client for interacting with BitBucket API to fetch .prompt files.

    Supports:
    - Authentication with access tokens
    - Fetching file contents from repositories
    - Team-based access control through BitBucket permissions
    - Branch-specific file fetching
    """

    def __init__(self, config: dict[str, Any]):
        """
        Initialize the BitBucket client.

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Use a flat or explicitly allow-listed path — drop '..' segments from the file path / prompt_id
  2. Canonicalize and re-verify external paths (e.g. os.path.normpath then confirm the result stays under the allowed root) before passing them in
  3. Rename repository layout if legitimate files need relative-style references

Example fix

# before
client.get_file("prompts/../internal/admin.prompt")  # ValueError: path traversal detected

# after
client.get_file("internal/admin.prompt")  # reference the target directly
Defensive patterns

Strategy: validation

Validate before calling

import posixpath

def normalize_repo_path(root: str, file_path: str) -> str:
    if ".." in file_path.split("/"):
        raise ValueError("path traversal not allowed")
    normalized = posixpath.normpath(f"{root}/{file_path}").lstrip("/")
    if normalized.startswith("..") or normalized.startswith("/"):
        raise ValueError("path escapes allowed root")
    return normalized

Type guard

def is_within_root(root: str, p: str) -> bool:
    if not isinstance(p, str) or ".." in p.split("/"):
        return False
    n = posixpath.normpath(p)
    return not n.startswith("..") and not posixpath.isabs(n)

Try / catch

try:
    client.get_file(prompt_id)
except ValueError as e:
    if "path traversal" in str(e):
        # untrusted input: log the security event and reject the request entirely
        security_log.warning("rejected traversal path: %r", prompt_id)
        raise
    raise

Prevention

When it happens

Trigger: Passing a file path with a '..' segment such as 'prompts/../../secrets/config' to get_file; prompt_id sourced from untrusted user input concatenated into a path; test payloads for path traversal.

Common situations: Prompt IDs built by string concatenation with user input; attempting to reference files outside the configured prompts directory; security scanners probing the integration.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/b928d1981b6cee88. Report an issue: GitHub.