AI_ONLY_REPORT package: python-pip-26.0.1-2.1.hum1 ------ Summary: Path Traversal via Malicious Entry Point Name in Wheel Metadata: A malicious wheel can use traversal or absolute entry-point names so pip writes generated script wrappers outside the intended `scheme.scripts` directory and overwrites files writable by the installing user. Requirements to exploit: An attacker must induce a victim to install a malicious wheel. The wheel must contain crafted `console_scripts` or `gui_scripts` names with `../` traversal or absolute-path components in `entry_points.txt`. The resulting overwrite is limited by the permissions of the installing user, but pip’s wheel-install flow reaches the vulnerable write path in normal operation and sets `maker.clobber = True`, so existing writable files are replaced when reachable. Component affected: `github.com/pypa/pip` - wheel installation flow in `src/pip/_internal/operations/install/wheel.py`, vendored `distlib` script generation in `src/pip/_vendor/distlib/scripts.py`, and entry-point parsing in `src/pip/_vendor/distlib/util.py` Version affected: 26.0.1 (confirmed). Likely affects versions containing the same `get_entrypoints(...) -> get_console_script_specs(...) -> ScriptMaker._write_script(...)` flow without target-directory enforcement. Patch available: no (a minimal proposed fix is included below; upstream release status unknown) Version fixed (if any already): unknown Upstream coordination: Not yet notified. This report is the initial triage. CVSS: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H` `AV:N` - An attacker can distribute a malicious wheel through a package source or other network-delivered artifact. `AC:L` - Crafting traversal or absolute entry-point names in `entry_points.txt` is straightforward. `PR:N` - The attacker needs no privileges on the victim system. `UI:R` - The victim must install the malicious wheel. `S:U` - Impact remains within the installing user's security scope. `C:N` - The primitive is attacker-controlled file overwrite, not direct data disclosure by default. `I:H` - Escaping `scheme.scripts` can overwrite writable files outside the intended install directory. `A:H` - Overwriting critical writable files can break applications or system behavior. Impact: Likely Important. This is a real arbitrary file overwrite/path traversal flaw in pip’s wheel installation flow. Exploitation requires a victim to install a malicious wheel, and the overwrite is limited to paths writable by the installing user. The written content is constrained to pip’s generated entry-point wrapper format rather than fully arbitrary bytes, which narrows the direct confidentiality impact, but escaping `scheme.scripts` can still severely affect integrity and availability and can lead to code execution when privileged or security-sensitive targets are overwritten. Embargo: yes Reason: No official fix is available, and the bug turns a malicious wheel into a write primitive outside the intended installation directory during a normal `pip install` flow. Public disclosure before a fix would make malicious package distribution campaigns easier, especially where wheels are installed with elevated privileges. Suggested public date: 15-Jul-2026 Acknowledgement: Aisle Research Steps to reproduce: 1. From the unpacked source tree, run: ```bash python - <<'PY' import os, tempfile, sys sys.path.insert(0, 'src') from pip._internal.operations.install.wheel import PipScriptMaker base = tempfile.mkdtemp(prefix='pip-script-test-') scripts = os.path.join(base, 'bin') os.makedirs(scripts, exist_ok=True) absolute_target = os.path.join(tempfile.gettempdir(), 'pip-owned') maker = PipScriptMaker(None, scripts) maker.clobber = True maker.variants = {''} maker.set_mode = False for spec in [f'../../outside = os:path.join', f'{absolute_target} = os:path.join']: files = maker.make(spec) print(spec, '->', files[0], '=>', os.path.abspath(files[0])) PY ``` 2. Observe that the generated output path resolves outside `scripts` and that the file is written. 3. In a real installation context, a malicious wheel that places the same crafted names in `entry_points.txt` reaches the same write sink during `pip install` and can overwrite attacker-chosen files writable by the installing user. Mitigation: Do not install untrusted wheels, especially in privileged contexts. Avoid `sudo pip install` or other elevated wheel-install workflows until a fix is available. Reject or inspect wheels whose `entry_points.txt` contains path separators, `..`, or absolute entry-point names. Vulnerability Details pip already enforces path-traversal checks for wheel archive members extracted from the `.whl`, but generated entry-point wrappers are created later from metadata and do not go through that containment check. The vulnerable flow is: `get_entrypoints(distribution)` reads `console_scripts` and `gui_scripts` names from wheel metadata. `get_console_script_specs()` formats those names into script specifications without sanitizing the name component. `PipScriptMaker.make_multiple()` forwards the names to vendored `distlib`. `ScriptMaker._write_script()` uses `os.path.join(self.target_dir, name)` and writes the generated wrapper without verifying that the resolved path stays inside `target_dir`. Additional stage-5 verification confirmed that this is not limited to a direct `PipScriptMaker` API call: the full wheel-metadata path is reachable in both supported metadata backends, and absolute entry-point names are accepted in addition to `../` traversal. Relevant CWEs: `CWE-22` (Path Traversal) `CWE-73` (External Control of File Name or Path) Proposed Fix A minimal defense-in-depth fix is to enforce that each generated script path remains inside `target_dir` before writing: ```diff diff --git a/src/pip/_vendor/distlib/scripts.py b/src/pip/_vendor/distlib/scripts.py @@ for name in names: outname = os.path.join(self.target_dir, name) + target_dir = os.path.abspath(self.target_dir) + outname_abs = os.path.abspath(outname) + if os.path.commonpath([target_dir, outname_abs]) != target_dir: + raise ValueError("Invalid script name %r: path traversal/absolute path" % name) if use_launcher: # pragma: no cover n, e = os.path.splitext(outname) ``` Optionally, reject path separators and absolute paths earlier when parsing or validating entry-point names. ------ This report was generated using AI technology. Always review AI-generated content prior to use