{"record":{"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/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_internal/utils/pylock.py#L149-L185","documentation":"Raised as InstallationError by _package_dist_url (pylock.py:167) when a relative path in a pylock file that was loaded from a URL resolves to a different scheme or host than the pylock file itself. This is a path-traversal guard: when a remote pylock.toml contains a relative `path` field, pip joins it with the pylock's base URL; if the result's scheme/netloc differs (e.g. a `../` escape or `//evil.com` redirect), pip refuses to follow it to prevent fetching files from arbitrary locations.","triggerScenarios":"Loading a pylock file from an http(s):// URL whose package entry has a relative path that, after urljoin with the base, changes the scheme or netloc. The check at lines 164-166 compares urlsplit results; mismatch raises at 167.","commonSituations":"A pylock file hosted on a CDN or index whose package paths use ../../ or //other-host/ patterns. A lock file generated by a tool that emitted paths relative to a different base. Malicious or misconfigured lock files that attempt to redirect downloads.","solutions":["Ensure all relative paths in the pylock file resolve under the same scheme and host as the pylock URL.","Replace relative paths with absolute URLs in the lock file's url field for each package.","Host the pylock file locally (file:// or a local path) so relative paths resolve on the filesystem instead of being URL-joined.","Regenerate the pylock file from a trusted source (e.g. `pip lock` / pip-tools) so paths are consistent."],"exampleFix":"// before (pylock at https://index.example.com/locks/pylock.toml)\n[[packages]]\nname = \"pkg\"\npath = \"../../../other-host/pkg.whl\"\n\n// after\n[[packages]]\nname = \"pkg\"\nurl = \"https://index.example.com/wheels/pkg.whl\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urljoin, urlsplit\n\ndef validate_pylock_relative_path(pylock_url: str, rel_path: str) -> bool:\n    \"\"\"True if a relative path in a remote pylock stays on the same scheme/host.\"\"\"\n    resolved = urljoin(pylock_url, rel_path)\n    base = urlsplit(pylock_url)\n    target = urlsplit(resolved)\n    return (target.scheme, target.netloc) == (base.scheme, base.netloc)","typeGuard":"from urllib.parse import urljoin, urlsplit\n\ndef is_safe_pylock_path(pylock_url: str, path: str) -> bool:\n    \"\"\"True if the path resolves within the pylock URL's origin.\"\"\"\n    resolved = urljoin(pylock_url, path)\n    b, t = urlsplit(pylock_url), urlsplit(resolved)\n    return t.scheme == b.scheme and t.netloc == b.netloc","tryCatchPattern":null,"preventionTips":["Use absolute URLs (not relative paths) in remote pylock files for package locations.","Validate all relative paths in a remote lock file resolve to the same origin before installing.","Prefer locally-hosted lock files when directory/relative paths are needed."],"tags":["pylock","path-traversal","security","url","remote-lock"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}