BerriAI/litellm · error · HTTPException

git-subdir 'path' must be a relative path of the form 'segme

Error message

git-subdir 'path' must be a relative path of the form 'segment/segment' (alphanumeric, dots, hyphens, underscores only)

What it means

400 thrown when a git-subdir source's "path" fails the allowlist regex ^[a-zA-Z0-9][a-zA-Z0-9._-]*(/[a-zA-Z0-9][a-zA-Z0-9._-]*)*$ — one or more '/'-separated segments, each starting alphanumeric and containing only alphanumerics, dots, hyphens, underscores. This is a security guard: it blocks path traversal ('..'), absolute paths, backslashes, and percent-encoded sequences before the path is ever used against a git checkout.

Source

Thrown at litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py:195

            raise HTTPException(
                status_code=400,
                detail={"error": "URL source must include 'url' field (e.g., 'https://github.com/org/repo.git')"},
            )
    elif source_type == "git-subdir":
        if not source.get("url"):
            raise HTTPException(
                status_code=400,
                detail={
                    "error": "git-subdir source must include 'url' field (e.g., 'https://github.com/org/repo.git')"
                },
            )
        if not source.get("path"):
            raise HTTPException(
                status_code=400,
                detail={"error": "git-subdir source must include 'path' field (e.g., 'plugins/plugin-name')"},
            )
        if not _VALID_GIT_SUBDIR_PATH_RE.match(source["path"]):
            raise HTTPException(
                status_code=400,
                detail={
                    "error": "git-subdir 'path' must be a relative path of the form 'segment/segment' (alphanumeric, dots, hyphens, underscores only)"
                },
            )
    else:
        raise HTTPException(
            status_code=400,
            detail={"error": "source.source must be 'github', 'url', or 'git-subdir'"},
        )


def _build_plugin_manifest(name: str, spec: PluginSpec) -> Mapping[str, object]:
    """Build the stored manifest dict shared by plugin create and update."""
    dumped: Final[Mapping[str, object]] = spec.model_dump(exclude_none=True)
    return {"name": name, **{key: value for key, value in dumped.items() if value and key != "name"}}

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Use a plain relative path of allowed segments, e.g. "plugins/my-plugin" or "packages/pkg.name".
  2. Remove any leading '/', '..', backslashes, or percent-encoding before sending; strip and re-join the path client-side if it comes from user input.
  3. If the real plugin directory starts with a dot or other disallowed character, rename the directory in the repo rather than fighting the allowlist.

Example fix

# before
{"source": {"source": "git-subdir", "url": "https://github.com/org/repo.git", "path": "../secrets"}}

# after
{"source": {"source": "git-subdir", "url": "https://github.com/org/repo.git", "path": "plugins/plugin-name"}}
Defensive patterns

Strategy: validation

Validate before calling

import re
VALID = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*(/[a-zA-Z0-9][a-zA-Z0-9._-]*)*$")

path = source["path"]
path = path.replace("\\", "/")
if not VALID.match(path) or ".." in path.split("/"):
    raise ValueError(f"git-subdir path {path!r} not allowed; use 'segment/segment' form")
source["path"] = path

Type guard

const GIT_SUBDIR_PATH = /^[a-zA-Z0-9][a-zA-Z0-9._-]*(\/[a-zA-Z0-9][a-zA-Z0-9._-]*)*$/;
function isSafeSubdirPath(p: unknown): p is string {
  return typeof p === 'string' && GIT_SUBDIR_PATH.test(p) && !p.split('/').includes('..');
}

Prevention

When it happens

Trigger: Registering a plugin with path values like "../other-plugin", "/plugins/my-plugin", "..\\plugins\\my-plugin", "%2e%2e/plugins", "plugins//my-plugin" (empty segment), or ".hidden/plugin" (segment starting with a dot) — all fail the regex and get this 400.

Common situations: Pointing the path outside the intended directory (traversal attempts or copy-pasted absolute paths from a local checkout); Windows-style backslash paths; URL-encoded paths passed through a client that pre-encodes; leading-dot directories like '.github'.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/cf54f928f00933a8. Report an issue: GitHub.