pypa/pip · error · ValueError

paths must be inside source tree

Error message

paths must be inside source tree

What it means

pyproject_hooks' norm_and_check raises ValueError('paths must be inside source tree') when a relative backend-path entry, after normalization, escapes the source tree. This guards against directory traversal: an entry like '../sibling' would resolve outside the project and is rejected.

Source

Thrown at src/pip/_vendor/pyproject_hooks/_impl.py:121

    """Normalise and check a backend path.

    Ensure that the requested backend path is specified as a relative path,
    and resolves to a location under the given source tree.

    Return an absolute version of the requested path.
    """
    if os.path.isabs(requested):
        raise ValueError("paths must be relative")

    abs_source = os.path.abspath(source_tree)
    abs_requested = os.path.normpath(os.path.join(abs_source, requested))
    # We have to use commonprefix for Python 2.7 compatibility. So we
    # normalise case to avoid problems because commonprefix is a character
    # based comparison :-(
    norm_source = os.path.normcase(abs_source)
    norm_requested = os.path.normcase(abs_requested)
    if os.path.commonprefix([norm_source, norm_requested]) != norm_source:
        raise ValueError("paths must be inside source tree")

    return abs_requested


class BuildBackendHookCaller:
    """A wrapper to call the build backend hooks for a source directory."""

    def __init__(
        self,
        source_dir: str,
        build_backend: str,
        backend_path: Optional[Sequence[str]] = None,
        runner: Optional["SubprocessRunner"] = None,
        python_executable: Optional[str] = None,
    ) -> None:
        """
        :param source_dir: The source directory to invoke the build backend for
        :param build_backend: The build backend spec

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Move the backend code inside the source tree and use a path with no '..' components.
  2. Remove the offending traversal entry from backend-path in pyproject.toml.
  3. Ensure source_dir passed to BuildBackendHookCaller is the project root the backend lives under.

Example fix

# before
[build-system]
backend-path = ["../shared_backend"]

# after
[build-system]
backend-path = ["shared_backend"]  # after moving it inside the project
Defensive patterns

Strategy: validation

Validate before calling

import os
abs_src = os.path.abspath(source_tree)
for p in backend_paths:
    abs_p = os.path.normpath(os.path.join(abs_src, p))
    if os.path.commonprefix([os.path.normcase(abs_src), os.path.normcase(abs_p)]) != os.path.normcase(abs_src):
        raise ValueError(f'backend-path escapes source tree: {p!r}')

Type guard

def is_inside_tree(source_tree: str, p: str) -> bool:
    import os
    a = os.path.normcase(os.path.normpath(os.path.join(os.path.abspath(source_tree), p)))
    s = os.path.normcase(os.path.abspath(source_tree))
    return a == s or a.startswith(s + os.sep)

Try / catch

try:
    caller = BuildBackendHookCaller(src, backend, backend_path=paths)
except ValueError:
    paths = [p for p in paths if not p.startswith('..')]
    caller = BuildBackendHookCaller(src, backend, backend_path=paths)

Prevention

When it happens

Trigger: Constructing BuildBackendHookCaller with backend_path containing a traversal such as '../backend' or subpaths that, once joined and normcased, no longer share the source tree prefix (os.path.commonprefix check fails).

Common situations: Backend code living outside the project directory referenced via '../'; symlinks or case-insensitive path mismatches on macOS/Windows; refactoring that moved the backend without updating pyproject.toml.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/759e5355523c6914.json. Report an issue: GitHub.