pypa/pip · error · InstallationError

Path {path!r} in pylock file obtained from a URL resolves ou

Error message

Path {path!r} in pylock file obtained from a URL resolves outside its location: {pylock_path_or_url!r}

What it means

InstallationError from _package_dist_url when a pylock.toml was fetched from a URL and a package's relative `path` resolves to a different scheme/netloc than the lock file itself. This is a path-traversal guard: a remote lock file must not pull artifacts from an arbitrary other host.

Source

Thrown at src/pip/_internal/utils/pylock.py:167

) -> str:
    """Compute an url from a Pylock package path and url.

    Give priority to path over url. If path is relative,
    compute an url using the pylock file location as base.
    """
    if path is not None:
        if not os.path.isabs(path):
            # relative path, join to pylock location
            if _is_url(pylock_path_or_url):
                dist_url = urljoin(pylock_path_or_url, path)
                # os.path.isabs does not treat a scheme-carrying value like
                # "file:..." as absolute, so it reaches here and urljoin honors
                # its scheme, discarding the pylock base. Only keep the result
                # if its scheme and host still match the lock's own.
                base = urlsplit(pylock_path_or_url)
                target = urlsplit(dist_url)
                if (target.scheme, target.netloc) != (base.scheme, base.netloc):
                    raise InstallationError(
                        f"Path {path!r} in pylock file obtained from a URL "
                        f"resolves outside its location: {pylock_path_or_url!r}"
                    )
                return dist_url
            else:
                return path_to_url(
                    os.path.join(os.path.dirname(pylock_path_or_url), path)
                )
        else:
            # absolute path, reject if pylock comes from a URL
            if _is_url(pylock_path_or_url):
                raise InstallationError(
                    f"Absolute paths are not supported in pylock files obtained "
                    f"from a URL: {path!r} in {pylock_path_or_url!r}"
                )
            return path_to_url(path)
    else:
        assert url is not None  # guaranteed by packaging.pylock validation

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Make package paths in the remote lock either bare URLs (use the `url` field) or paths that resolve under the same scheme+host.
  2. If you need cross-host artifacts, host the lock file locally (file path, not URL) so the relative-path branch isn't used.
  3. Verify the lock was generated by a trusted tool (not hand-edited) so paths stay in-bounds.

Example fix

# before - remote lock with escaping relative path
# pylock.toml served at https://a.com/lock.toml
[[packages]]
path = "//evil.com/p/pkg-1.0.whl"

# after - use an in-host url instead
[[packages]]
url = "https://a.com/p/pkg-1.0.whl"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit, urljoin

def pylock_relative_path_in_bounds(lock_url, path):
    resolved = urljoin(lock_url, path)
    b, t = urlsplit(lock_url), urlsplit(resolved)
    return (b.scheme, b.netloc) == (t.scheme, t.netloc)
# reject the lock entry before passing it to pip

Try / catch

try:
    pip_install('-r', lock_url)
except InstallationError as e:
    if 'resolves outside its location' in str(e):
        rewrite_lock_paths_to_urls(lock_url)
        pip_install('-r', lock_url)
    else:
        raise

Prevention

When it happens

Trigger: _is_url(pylock_path_or_url) and path is relative; urljoin produces dist_url whose (scheme,netloc) differs from the base lock URL. E.g. lock at https://corp.example.com/lock.toml with path='../../evil.com/pkg' or path='file:///etc/passwd' that urljoin redirects to a different host.

Common situations: A remote pylock.toml (pip -r https://...) whose paths are written for a local filesystem and get reinterpreted against the URL base; a hand-authored lock with absolute-looking relative paths that escape the host; mirrored lock files copied between hosts.

Related errors


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