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 validationView on GitHub (pinned to d7d0d0a394)
Solutions
- Make package paths in the remote lock either bare URLs (use the `url` field) or paths that resolve under the same scheme+host.
- If you need cross-host artifacts, host the lock file locally (file path, not URL) so the relative-path branch isn't used.
- 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
- For remote locks, prefer the `url` field over relative `path` fields.
- Generate remote locks with a trusted tool; don't hand-edit paths.
- Serve directory/path-style locks only from local file paths.
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
- Absolute paths are not supported in pylock files obtained fr
- Package URL {url!r} cannot contain fragments in combination
- Directory entries are not supported in remote pylock.toml {p
- Error reading pylock file {pylock_path_or_url!r}: {exc}
- Hashes are required in --require-hashes mode, but they are m
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/f516525eb406a864.json.
Report an issue: GitHub.