{"id":"f516525eb406a864","repo":"pypa/pip","slug":"path-path-r-in-pylock-file-obtained-from-a-url-r","errorCode":null,"errorMessage":"Path {path!r} in pylock file obtained from a URL resolves outside its location: {pylock_path_or_url!r}","messagePattern":"Path (.+?) in pylock file obtained from a URL resolves outside its location: (.+?)","errorType":"exception","errorClass":"InstallationError","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/utils/pylock.py","lineNumber":167,"sourceCode":") -> str:\n    \"\"\"Compute an url from a Pylock package path and url.\n\n    Give priority to path over url. If path is relative,\n    compute an url using the pylock file location as base.\n    \"\"\"\n    if path is not None:\n        if not os.path.isabs(path):\n            # relative path, join to pylock location\n            if _is_url(pylock_path_or_url):\n                dist_url = urljoin(pylock_path_or_url, path)\n                # os.path.isabs does not treat a scheme-carrying value like\n                # \"file:...\" as absolute, so it reaches here and urljoin honors\n                # its scheme, discarding the pylock base. Only keep the result\n                # if its scheme and host still match the lock's own.\n                base = urlsplit(pylock_path_or_url)\n                target = urlsplit(dist_url)\n                if (target.scheme, target.netloc) != (base.scheme, base.netloc):\n                    raise InstallationError(\n                        f\"Path {path!r} in pylock file obtained from a URL \"\n                        f\"resolves outside its location: {pylock_path_or_url!r}\"\n                    )\n                return dist_url\n            else:\n                return path_to_url(\n                    os.path.join(os.path.dirname(pylock_path_or_url), path)\n                )\n        else:\n            # absolute path, reject if pylock comes from a URL\n            if _is_url(pylock_path_or_url):\n                raise InstallationError(\n                    f\"Absolute paths are not supported in pylock files obtained \"\n                    f\"from a URL: {path!r} in {pylock_path_or_url!r}\"\n                )\n            return path_to_url(path)\n    else:\n        assert url is not None  # guaranteed by packaging.pylock validation","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/utils/pylock.py#L149-L185","documentation":"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.","triggerScenarios":"_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.","commonSituations":"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.","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."],"exampleFix":"# before - remote lock with escaping relative path\n# pylock.toml served at https://a.com/lock.toml\n[[packages]]\npath = \"//evil.com/p/pkg-1.0.whl\"\n\n# after - use an in-host url instead\n[[packages]]\nurl = \"https://a.com/p/pkg-1.0.whl\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit, urljoin\n\ndef pylock_relative_path_in_bounds(lock_url, path):\n    resolved = urljoin(lock_url, path)\n    b, t = urlsplit(lock_url), urlsplit(resolved)\n    return (b.scheme, b.netloc) == (t.scheme, t.netloc)\n# reject the lock entry before passing it to pip","typeGuard":null,"tryCatchPattern":"try:\n    pip_install('-r', lock_url)\nexcept InstallationError as e:\n    if 'resolves outside its location' in str(e):\n        rewrite_lock_paths_to_urls(lock_url)\n        pip_install('-r', lock_url)\n    else:\n        raise","preventionTips":["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."],"tags":["pip","pylock","security","url","path-traversal","lockfile"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}