Compare commits

..

1 commit

Author SHA1 Message Date
Lumir Balhar
40c9b4419c Security fixes for CVE-2026-13346 and CVE-2026-8643 2026-08-26 08:52:14 +02:00
10 changed files with 270 additions and 224 deletions

View file

@ -1,38 +0,0 @@
From 09a03f6cfaeecae8bf5774ae371d37c4369e2da4 Mon Sep 17 00:00:00 2001
From: Damian Shaw <damian.peter.shaw@gmail.com>
Date: Sat, 15 Aug 2026 13:36:05 -0400
Subject: [PATCH] Allow flit-core 4 to build pip
---
pyproject.toml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 48f64c8018..fe6b39913d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,13 +45,13 @@ Source = "https://github.com/pypa/pip"
Changelog = "https://pip.pypa.io/en/stable/news/"
[build-system]
-requires = ["flit-core >=3.11,<4"]
+requires = ["flit-core >=3.11,<5"]
build-backend = "flit_core.buildapi"
[dependency-groups]
test = [
"cryptography",
- "flit-core >= 3.11, < 4",
+ "flit-core >= 3.11, < 5",
"freezegun",
"installer",
# pytest-subket requires 7.0+
@@ -69,7 +69,7 @@ test = [
]
test-common-wheels = [
- "flit-core >= 3.11, < 4",
+ "flit-core >= 3.11, < 5",
# We pin setuptools<80 because our test suite currently
# depends on setup.py develop to generate egg-link files.
"setuptools >= 70.1.0, <80",

View file

@ -1,29 +0,0 @@
From 4c6d7471dec62fb004a47a7c2164b6b5b089ac06 Mon Sep 17 00:00:00 2001
From: Richard Si <sichard26@gmail.com>
Date: Fri, 5 Jun 2026 15:44:14 -0400
Subject: [PATCH] Also fix user site patching in test suite
---
tests/lib/venv.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/lib/venv.py b/tests/lib/venv.py
index 67b01d9f31..4e86b92b3b 100644
--- a/tests/lib/venv.py
+++ b/tests/lib/venv.py
@@ -174,11 +174,13 @@ def _customize_site(self) -> None:
site.ENABLE_USER_SITE = {self._user_site_packages}
# First, drop system-sites related paths.
original_sys_path = sys.path[:]
+ # To discover system-sites related paths, clear sys.path
+ # and build a new one with only system paths.
+ sys.path = []
known_paths = set()
for path in site.getsitepackages():
site.addsitedir(path, known_paths=known_paths)
- system_paths = sys.path[len(original_sys_path):]
- for path in system_paths:
+ for path in sys.path:
if path in original_sys_path:
original_sys_path.remove(path)
sys.path = original_sys_path

View file

@ -1,62 +0,0 @@
From 6099a54dddbfbc7fb912d53b6adad5ff6b8d1745 Mon Sep 17 00:00:00 2001
From: Richard Si <sichard26@gmail.com>
Date: Fri, 5 Jun 2026 15:03:38 -0400
Subject: [PATCH] Fix sitecustomize.py used for build isolation on Python 3.15+
The sitecustomize.py file pip uses to isolate build subprocesses from
the parent environment discovers system related paths by calling
site.addsitedir() for every system site-packages path and observing
what new entries are appended to sys.path.
This breaks since Python 3.15b2 due to two changes:
- site.addsitedir() won't add a path if it already exists in sys.path
- site.addsitedir() won't re-execute .pth files if called for a known
directory (which includes the system sites because known_path is
mutated by addsitedir before it checks for .pth files)
To cope with this, temporarily clear sys.path before using
site.addsitedir() to discover all system paths for exclusion.
---
news/14033.bugfix.rst | 1 +
src/pip/_internal/build_env.py | 14 +++++++++-----
2 files changed, 10 insertions(+), 5 deletions(-)
create mode 100644 news/14033.bugfix.rst
diff --git a/news/14033.bugfix.rst b/news/14033.bugfix.rst
new file mode 100644
index 0000000000..404196a2f0
--- /dev/null
+++ b/news/14033.bugfix.rst
@@ -0,0 +1 @@
+Prevent system packages from leaking into isolated build environments on Python 3.15
diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py
index 1a42a9d411..7639dabcad 100644
--- a/src/pip/_internal/build_env.py
+++ b/src/pip/_internal/build_env.py
@@ -468,15 +468,19 @@ def __init__(self, installer: BuildEnvironmentInstaller) -> None:
"""
import os, site, sys
- # First, drop system-sites related paths.
+ # First, discover all system-sites related paths.
original_sys_path = sys.path[:]
+ # Clear sys.path so addsitedir() will add system site paths and paths
+ # added by contained .pth files to sys.path reliably. This is necessary
+ # since Python 3.15, which notably no longer re-executes .pth files for
+ # known paths.
+ sys.path = []
known_paths = set()
for path in {system_sites!r}:
site.addsitedir(path, known_paths=known_paths)
- system_paths = set(
- os.path.normcase(path)
- for path in sys.path[len(original_sys_path):]
- )
+ system_paths = set(os.path.normcase(path) for path in sys.path)
+
+ # Drop discovered system-sites related paths.
original_sys_path = [
path for path in original_sys_path
if os.path.normcase(path) not in system_paths

View file

@ -1,37 +1,30 @@
From 10dfb6b9005484578b386f64b9f36982e3dc6679 Mon Sep 17 00:00:00 2001
From 6686b964762255a933e68939bb49710216ae7bb9 Mon Sep 17 00:00:00 2001
From: Damian Shaw <damian.peter.shaw@gmail.com>
Date: Tue, 30 Jun 2026 21:52:39 -0400
Subject: [PATCH] Fix Link.filename decoding URL path twice (#14110)
Date: Fri, 14 Aug 2026 09:49:19 +0000
Subject: [PATCH 2/2] Fix Link.filename double URL decode - path traversal
(CVE-2026-13346)
Link already percent-decodes the URL path into `self._path`, but
`Link.filename` decoded the basename again, so a doubly-encoded
separator was decoded twice: `%252F` became `%2F` in `__init__`, then
`/` in `filename`, turning the single component `a%2Fb.whl` into
`a/b.whl`.
Upstream PRs: https://github.com/pypa/pip/pull/14110
Drop the second decode, and add a `join_within_directory` helper so the
download-path joins treat the name as a single path component.
- Add PathComponent newtype to enforce single-component filenames
- Remove double urllib.parse.unquote() call in Link.filename
- Add join_within_directory() preventing path escape at download sites
- Update download.py and prepare.py call sites
- Backport test coverage from upstream commits 1, 3, 4 of PR #14110
Co-Authored-By: Lumir Balhar <lbalhar@redhat.com>
---
news/14110.bugfix.rst | 1 +
src/pip/_internal/models/link.py | 63 ++++++++++---
src/pip/_internal/models/link.py | 61 ++++++++++---
src/pip/_internal/network/download.py | 20 +++--
src/pip/_internal/operations/prepare.py | 6 +-
tests/unit/test_link.py | 113 +++++++++++++++++++++++-
5 files changed, 182 insertions(+), 21 deletions(-)
create mode 100644 news/14110.bugfix.rst
4 files changed, 179 insertions(+), 21 deletions(-)
diff --git a/news/14110.bugfix.rst b/news/14110.bugfix.rst
new file mode 100644
index 0000000000..f7d4f78882
--- /dev/null
+++ b/news/14110.bugfix.rst
@@ -0,0 +1 @@
+Fix ``Link.filename`` decoding the URL path twice.
diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py
index 0a09c66222..cbbe945c17 100644
index 200ec34..e6a0c87 100644
--- a/src/pip/_internal/models/link.py
+++ b/src/pip/_internal/models/link.py
@@ -13,6 +13,7 @@
@@ -14,6 +14,7 @@ from dataclasses import dataclass
from typing import (
Any,
NamedTuple,
@ -39,7 +32,7 @@ index 0a09c66222..cbbe945c17 100644
)
from pip._internal.exceptions import InvalidEggFragment
@@ -30,6 +31,49 @@
@@ -31,6 +32,47 @@ from pip._internal.utils.urls import path_to_url, url_to_path
logger = logging.getLogger(__name__)
@ -80,8 +73,6 @@ index 0a09c66222..cbbe945c17 100644
+
+ ``component`` is a :data:`PathComponent`, so by type it has no separator and
+ is not a ``.`` or ``..`` reference; the result can never escape ``directory``.
+ Requiring ``PathComponent`` rather than ``str`` lets the type checker enforce
+ at the call site that the name was reduced to a safe component beforehand.
+ """
+ return os.path.join(directory, component)
+
@ -89,7 +80,7 @@ index 0a09c66222..cbbe945c17 100644
# Order matters, earlier hashes have a precedence over later hashes for what
# we will pick to use.
_SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
@@ -424,18 +468,13 @@ def redacted_url(self) -> str:
@@ -423,18 +465,13 @@ class Link:
return redact_auth_from_url(self.url)
@property
@ -116,13 +107,13 @@ index 0a09c66222..cbbe945c17 100644
@property
def file_path(self) -> str:
diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py
index 039b268878..6faafb5cb0 100644
index 2696642..fa71c75 100644
--- a/src/pip/_internal/network/download.py
+++ b/src/pip/_internal/network/download.py
@@ -19,7 +19,12 @@
@@ -20,7 +20,12 @@ from pip._vendor.urllib3.exceptions import ReadTimeoutError
from pip._internal.cli.progress_bars import BarType, get_download_progress_renderer
from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
from pip._internal.models.index import PyPI
-from pip._internal.models.link import Link
+from pip._internal.models.link import (
+ Link,
@ -133,7 +124,7 @@ index 039b268878..6faafb5cb0 100644
from pip._internal.network.cache import SafeFileCache, is_from_cache
from pip._internal.network.session import CacheControlAdapter, PipSession
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
@@ -121,11 +126,14 @@ def parse_content_disposition(content_disposition: str, default_filename: str) -
@@ -117,11 +122,14 @@ def parse_content_disposition(content_disposition: str, default_filename: str) -
return filename or default_filename
@ -150,7 +141,7 @@ index 039b268878..6faafb5cb0 100644
# Have a look at the Content-Disposition header for a better guess
content_disposition = resp.headers.get("content-disposition")
if content_disposition:
@@ -139,7 +147,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
@@ -135,7 +143,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
ext = os.path.splitext(resp.url)[1]
if ext:
filename += ext
@ -159,7 +150,7 @@ index 039b268878..6faafb5cb0 100644
@dataclass
@@ -192,7 +200,9 @@ def __call__(self, link: Link, location: str) -> tuple[str, str]:
@@ -188,7 +196,9 @@ class Downloader:
resp = self._http_get(link)
download_size = _get_http_response_size(resp)
@ -171,13 +162,13 @@ index 039b268878..6faafb5cb0 100644
download = _FileDownload(link, content_file, download_size)
self._process_response(download, resp)
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index afcc0376da..3b44403e0d 100644
index 67f9ee9..d260d15 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -29,7 +29,7 @@
@@ -29,7 +29,7 @@ from pip._internal.exceptions import (
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution, get_metadata_distribution
from pip._internal.models.direct_url import ArchiveInfo, DirectUrl
from pip._internal.models.direct_url import ArchiveInfo
-from pip._internal.models.link import Link
+from pip._internal.models.link import Link, join_within_directory
from pip._internal.models.wheel import Wheel
@ -192,7 +183,7 @@ index afcc0376da..3b44403e0d 100644
if not os.path.exists(download_path):
return None
@@ -687,7 +687,7 @@ def save_linked_requirement(self, req: InstallRequirement) -> None:
@@ -683,7 +683,7 @@ class RequirementPreparer:
# No distribution was downloaded for this requirement.
return
@ -202,7 +193,7 @@ index afcc0376da..3b44403e0d 100644
shutil.copy(req.local_file_path, download_location)
download_path = display_path(download_location)
diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py
index c49f8547ac..bc8cb8ab9b 100644
index c49f854..bc8cb8a 100644
--- a/tests/unit/test_link.py
+++ b/tests/unit/test_link.py
@@ -1,9 +1,17 @@
@ -224,7 +215,7 @@ index c49f8547ac..bc8cb8ab9b 100644
from pip._internal.utils.hashes import Hashes
@@ -29,6 +37,13 @@ def test_repr(self, url: str, expected: str) -> None:
@@ -29,6 +37,13 @@ class TestLink:
("https://example.com/path/page.html", "page.html"),
# Test a quoted character.
("https://example.com/path/page%231.html", "page#1.html"),
@ -238,7 +229,7 @@ index c49f8547ac..bc8cb8ab9b 100644
(
"http://yo/myproject-1.0%2Bfoobar.0-py2.py3-none-any.whl",
"myproject-1.0+foobar.0-py2.py3-none-any.whl",
@@ -49,6 +64,52 @@ def test_filename(self, url: str, expected: str) -> None:
@@ -49,6 +64,52 @@ class TestLink:
link = Link(url)
assert link.filename == expected
@ -345,3 +336,6 @@ index c49f8547ac..bc8cb8ab9b 100644
+ joined = join_within_directory(directory, as_path_component(name))
+ assert joined == os.path.join(directory, name)
+ assert os.path.basename(joined) == name
--
2.55.0

139
CVE-2026-8643.patch Normal file
View file

@ -0,0 +1,139 @@
From c840c212830bb8e9dab56de6c57916b111858aec Mon Sep 17 00:00:00 2001
From: Damian Shaw <damian.peter.shaw@gmail.com>
Date: Mon, 18 May 2026 23:04:43 -0400
Subject: [PATCH 1/2] Reject entry point names that escape scripts dir
---
src/pip/_internal/operations/install/wheel.py | 26 +++++++-
tests/unit/test_wheel.py | 64 +++++++++++++++++++
2 files changed, 87 insertions(+), 3 deletions(-)
diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py
index 40097d6..231e400 100644
--- a/src/pip/_internal/operations/install/wheel.py
+++ b/src/pip/_internal/operations/install/wheel.py
@@ -397,11 +397,31 @@ class MissingCallableSuffix(InstallationError):
)
-def _raise_for_invalid_entrypoint(specification: str) -> None:
+def _script_within_dir(name: str, scripts_dir: str) -> bool:
+ """Return whether script ``name`` resolves to a path inside the ``scripts_dir``.
+
+ distlib joins the entry point name onto the scripts directory, so a name
+ with path separators or ``..`` components can resolve elsewhere.
+ """
+ root = os.path.normpath(scripts_dir)
+ dest = os.path.normpath(os.path.join(scripts_dir, name))
+ return dest.startswith(root + os.sep)
+
+
+def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None:
entry = get_export_entry(specification)
- if entry is not None and entry.suffix is None:
+ if entry is None:
+ return
+
+ if entry.suffix is None:
raise MissingCallableSuffix(str(entry))
+ if not _script_within_dir(entry.name, scripts_dir):
+ raise InstallationError(
+ f"Invalid script entry point name {entry.name!r}: the script "
+ f"would be installed outside the scripts directory ({scripts_dir})."
+ )
+
class PipScriptMaker(ScriptMaker):
# Override distlib's default script template with one that
@@ -419,7 +439,7 @@ class PipScriptMaker(ScriptMaker):
def make(
self, specification: str, options: dict[str, Any] | None = None
) -> list[str]:
- _raise_for_invalid_entrypoint(specification)
+ _raise_for_invalid_entrypoint(specification, self.target_dir)
return super().make(specification, options)
diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py
index e0ac649..c1aafe5 100644
--- a/tests/unit/test_wheel.py
+++ b/tests/unit/test_wheel.py
@@ -462,6 +462,32 @@ class TestInstallUnpackedWheel:
assert os.path.basename(wheel_path) in exc_text
assert entrypoint in exc_text
+ @pytest.mark.parametrize("bad_name", ["../../outside", "..", "."])
+ @pytest.mark.parametrize("entry_point_type", ["console_scripts", "gui_scripts"])
+ def test_wheel_install_rejects_entry_point_path_traversal(
+ self, data: TestData, tmpdir: Path, bad_name: str, entry_point_type: str
+ ) -> None:
+ """An entry point name with separators or ``..`` must not install a
+ script outside the scripts directory.
+ """
+ self.prep(data, tmpdir)
+ wheel_path = make_wheel(
+ "simple",
+ "0.1.0",
+ entry_points={entry_point_type: [f"{bad_name} = simple:main"]},
+ ).save_to_dir(tmpdir)
+ with pytest.raises(InstallationError) as e:
+ wheel.install_wheel(
+ "simple",
+ str(wheel_path),
+ scheme=self.scheme,
+ req_description="simple",
+ )
+
+ assert "outside the scripts directory" in str(e.value)
+ # Nothing was written outside the install destination.
+ assert not os.path.exists(os.path.join(str(tmpdir), "outside"))
+
class TestMessageAboutScriptsNotOnPATH:
tilde_warning_msg = (
@@ -665,3 +691,41 @@ def test_get_console_script_specs_replaces_python_version(
"not_pip_or_easy_install-99 = whatever",
"not_pip_or_easy_install-99.88 = whatever",
]
+
+
+@pytest.mark.parametrize(
+ "name, within",
+ [
+ ("pip", True),
+ ("pip3.13", True),
+ ("foo-bar.baz", True),
+ ("...", True), # a literal filename, not a path component
+ ("sub/script", True), # in-tree subdirectory
+ ("a/../b", True),
+ ("sub\\script", True), # backslash stays in-tree on POSIX and Windows
+ (" ../../inside", True), # distlib keeps a leading space; resolves in-tree
+ ("../outside", False),
+ ("../../outside", False),
+ ("a/../../outside", False),
+ ("/etc/cron.d/outside", False), # absolute path; os.path.join drops the root
+ # "." and ".." pass PyPI's [\w.-]+ name check but must be rejected here.
+ (".", False),
+ ("..", False),
+ ("", False),
+ ],
+)
+def test_script_within_dir(name: str, within: bool) -> None:
+ assert wheel._script_within_dir(name, "/srv/env/bin") is within
+
+
+def test_script_within_dir_allows_doubled_slash_root() -> None:
+ # A scripts directory can have a doubled leading slash
+ assert wheel._script_within_dir("pip", "//srv/env/bin") is True
+ assert wheel._script_within_dir("../outside", "//srv/env/bin") is False
+
+
+@pytest.mark.skipif(not WINDOWS, reason="drive letters only matter on Windows")
+def test_script_within_dir_rejects_other_drive() -> None:
+ # Validate that a script on a different drive is rejected,
+ # and doesn't throw an error
+ assert wheel._script_within_dir("D:\\outside", "C:\\env\\bin") is False
--
2.55.0

View file

@ -6,17 +6,12 @@ discover:
how: shell
url: https://src.fedoraproject.org/tests/python.git
tests:
- name: smoke
- name: smoke36
path: /smoke
test: ./venv.sh
- name: smoke_virtualenv
test: VERSION=3.6 TOX=false ./venv.sh
- name: smoke39
path: /smoke
test: METHOD=virtualenv ./venv.sh
- name: tests_python_fedora_only
how: shell
url: https://src.fedoraproject.org/tests/python.git
when: distro != fedora-eln and distro == fedora
tests:
test: VERSION=3.9 ./venv.sh
- name: smoke310
path: /smoke
test: VERSION=3.10 ./venv.sh
@ -35,6 +30,9 @@ discover:
- name: smoke315
path: /smoke
test: VERSION=3.15 ./venv.sh
- name: smoke39_virtualenv
path: /smoke
test: VERSION=3.9 METHOD=virtualenv ./venv.sh
- name: smoke310_virtualenv
path: /smoke
test: VERSION=3.10 METHOD=virtualenv ./venv.sh
@ -90,6 +88,14 @@ prepare:
package:
- gcc
- virtualenv
- python3.6-devel
- python3.9-devel
- python3.10-devel
- python3.11-devel
- python3.12-devel
- python3.13-devel
- python3.14-devel
- python3.15-devel
- python3-devel
- python3-tox
- mock
@ -100,16 +106,6 @@ prepare:
- shadow-utils
- expect
- dnf
- name: Install dependencies (Fedora not ELN)
how: install
when: distro != fedora-eln and distro == fedora
package:
- python3.10-devel
- python3.11-devel
- python3.12-devel
- python3.13-devel
- python3.14-devel
- python3.15-devel
- name: Update packages
how: shell
script: dnf upgrade -y

View file

@ -6,7 +6,7 @@
%bcond man 1
%global srcname pip
%global base_version 26.1.2
%global base_version 26.0.1
%global upstream_version %{base_version}%{?prerel}
%global python_wheel_name %{srcname}-%{upstream_version}-py3-none-any.whl
@ -20,6 +20,7 @@ Summary: A tool for installing and managing Python packages
# certifi: MPL-2.0
# CacheControl: Apache-2.0
# dependency-groups: MIT
# distlib: Python-2.0.1
# distro: Apache-2.0
# idna: BSD-3-Clause
@ -95,24 +96,18 @@ Patch: dummy-certifi.patch
# We don't need a layer to check that, as we're by default in an offline environment
Patch: downstream-remove-pytest-subket.patch
# Fix sitecustomize.py used for build isolation on Python 3.15+
Patch: https://github.com/pypa/pip/commit/6099a54ddd.patch
# Patch for the bundled urllib3 for CVE-2025-50181
# Redirects are not disabled when retries are disabled on PoolManager instantiation
# Upstream fix: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
Patch: urllib3-CVE-2025-50181.patch
# Fix user-site path ordering in the test suite on Python 3.15+
# The same CPython gh-149819 change that broke build env isolation also broke
# _customize_site() in tests/lib/venv.py: site.addsitedir() no longer
# re-appends paths already in sys.path, so the detection of system-site paths
# produces an empty list and user site ends up after venv site-packages instead
# of before it, causing user-site install/uninstall tests to operate on the
# wrong installation.
Patch: https://github.com/pypa/pip/commit/4c6d7471de.patch
# Allow flit-core 4 to build pip
# https://github.com/pypa/pip/commit/09a03f6cfa (non-existing files removed)
Patch: 09a03f6cfa.patch
# CVE-2026-8643: entry point path traversal in console_scripts/gui_scripts
# Upstream fix: https://github.com/pypa/pip/pull/14000
Patch: CVE-2026-8643.patch
# CVE-2026-13346: Link.filename double URL decode allows path traversal
Patch: https://github.com/pypa/pip/commit/10dfb6b900.patch
# Upstream fix: https://github.com/pypa/pip/pull/14110
Patch: CVE-2026-13346.patch
# Remove -s from Python shebang - ensure that packages installed with pip
# to user locations are seen by pip itself
@ -131,23 +126,24 @@ Packages" or "Pip Installs Python".
# %%{_rpmconfigdir}/pythonbundles.py --namespace 'python%%{1}dist' src/pip/_vendor/vendor.txt
%global bundled() %{expand:
Provides: bundled(python%{1}dist(cachecontrol)) = 0.14.4
Provides: bundled(python%{1}dist(certifi)) = 2026.2.25
Provides: bundled(python%{1}dist(certifi)) = 2026.1.4
Provides: bundled(python%{1}dist(dependency-groups)) = 1.3.1
Provides: bundled(python%{1}dist(distlib)) = 0.4
Provides: bundled(python%{1}dist(distro)) = 1.9
Provides: bundled(python%{1}dist(idna)) = 3.11
Provides: bundled(python%{1}dist(msgpack)) = 1.1.2
Provides: bundled(python%{1}dist(packaging)) = 26.2
Provides: bundled(python%{1}dist(packaging)) = 26
Provides: bundled(python%{1}dist(platformdirs)) = 4.5.1
Provides: bundled(python%{1}dist(pygments)) = 2.19.2
Provides: bundled(python%{1}dist(pyproject-hooks)) = 1.2
Provides: bundled(python%{1}dist(requests)) = 2.33.1
Provides: bundled(python%{1}dist(requests)) = 2.32.5
Provides: bundled(python%{1}dist(resolvelib)) = 1.2.1
Provides: bundled(python%{1}dist(rich)) = 14.2
Provides: bundled(python%{1}dist(setuptools)) = 70.3
Provides: bundled(python%{1}dist(tomli)) = 2.3.1
Provides: bundled(python%{1}dist(tomli)) = 2.3
Provides: bundled(python%{1}dist(tomli-w)) = 1.2
Provides: bundled(python%{1}dist(truststore)) = 0.10.4
Provides: bundled(python%{1}dist(urllib3)) = 2.6.3
Provides: bundled(python%{1}dist(urllib3)) = 1.26.20
}
# Some manylinux1 wheels need libcrypt.so.1.
@ -189,9 +185,7 @@ Requires: ca-certificates
# Virtual provides for the packages bundled by pip:
%{bundled %{python3_pkgversion}}
%if "%{python3_pkgversion}" == "3"
Provides: pip = %{version}-%{release}
%endif
%description -n python%{python3_pkgversion}-%{srcname}
pip is a package management system used to install and manage software packages
@ -305,7 +299,7 @@ sed -i -e "s/^\\(complete.*\\) pip%{python3_version}\$/\\1 pip%{python3_version}
# Install the built wheel and inject SBOM into it (if the macro is available)
mkdir -p %{buildroot}%{python_wheel_dir}
install -pm0644 %{_pyproject_wheeldir}/%{python_wheel_name} -t %{buildroot}%{python_wheel_dir}
install -p %{_pyproject_wheeldir}/%{python_wheel_name} -t %{buildroot}%{python_wheel_dir}
%{?python_wheel_inject_sbom:%python_wheel_inject_sbom %{buildroot}%{python_wheel_dir}/%{python_wheel_name}}
@ -328,8 +322,9 @@ grep "pem$" %{pyproject_files} && exit 1 || true
pytest_k='not completion'
# this clashes with our PYTHONPATH
pytest_k="$pytest_k and not environments_with_no_pip"
# this requires internet without the keyring local wheel
pytest_k="$pytest_k and not test_prompt_for_keyring_if_needed"
# this seems to require internet (despite no network marker)
# added in https://github.com/pypa/pip/pull/13378 TODO drop this in the next release
pytest_k="$pytest_k and not test_prompt_for_keyring_if_needed and not test_double_install_fail and not test_install_sdist_links and not test_lock_vcs and not test_lock_archive and not test_backend_sees_config_via_sdist"
# this cannot import breezy, TODO investigate
pytest_k="$pytest_k and not (functional and bazaar)"
# failures to investigate

View file

@ -1,4 +1,4 @@
From a12217cf8f9bf3ab8e39d9bcc6b42e7043e79b08 Mon Sep 17 00:00:00 2001
From d5cb806a14bb50a96484bca6536e81f2ac316b9d Mon Sep 17 00:00:00 2001
From: Karolina Surma <ksurma@redhat.com>
Date: Wed, 16 Feb 2022 08:36:21 +0100
Subject: [PATCH] Prevent removing of the system packages installed under
@ -53,10 +53,10 @@ index 230e114..8bd5d31 100644
class BaseEnvironment:
"""An environment containing distributions to introspect."""
diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py
index aad2f0e..7b385b1 100644
index c9f6bff..c101826 100644
--- a/src/pip/_internal/req/req_install.py
+++ b/src/pip/_internal/req/req_install.py
@@ -439,7 +439,7 @@ class InstallRequirement:
@@ -453,7 +453,7 @@ class InstallRequirement:
f"lack sys.path precedence to {existing_dist.raw_name} "
f"in {existing_dist.location}"
)
@ -66,7 +66,7 @@ index aad2f0e..7b385b1 100644
else:
if self.editable:
diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py
index 6cc6311..4188ef1 100644
index 33a4fdc..1fe886e 100644
--- a/src/pip/_internal/resolution/legacy/resolver.py
+++ b/src/pip/_internal/resolution/legacy/resolver.py
@@ -322,7 +322,9 @@ class Resolver(BaseResolver):
@ -81,11 +81,11 @@ index 6cc6311..4188ef1 100644
req.satisfied_by = None
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index a74200a..99738cc 100644
index f23e4cd..1bada79 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -4,6 +4,8 @@ import contextlib
import copy
@@ -3,6 +3,8 @@ from __future__ import annotations
import contextlib
import functools
import logging
+import sys
@ -93,7 +93,7 @@ index a74200a..99738cc 100644
from collections.abc import Iterable, Iterator, Mapping, Sequence
from typing import (
TYPE_CHECKING,
@@ -674,6 +676,16 @@ class Factory:
@@ -615,6 +617,16 @@ class Factory:
if dist is None: # Not installed, no uninstallation required.
return None
@ -111,5 +111,5 @@ index a74200a..99738cc 100644
# be uninstalled, no matter it's in global or user site, because the
# user site installation has precedence over global.
--
2.54.0
2.50.1

View file

@ -1,4 +1,4 @@
SHA512 (pip-26.0.1.tar.gz) = 3786df7522ea65bc20fb9885ce5c2ddc60200a536a1f754a8d7dc278115c73258863e4c51ac7e9a60dda0b70263730dc194f70e1e8f8d00178a8b3c724333bf0
SHA512 (setuptools-79.0.1-py3-none-any.whl) = fef6cfc6f95a5bb7320f1680e1c665cb8d9a4e4227cde4d8aab8a50bed4bcf04320085b9d7d5343359f887008db5c5a861e57f3d08b7b0b2311a28adaeee6b4a
SHA512 (flit_core-3.12.0-py3-none-any.whl) = 790c12b1f43201e365fb3f8f2f0a54e1a578876799dfdf8bfeea679a25ea096bf62946d006618c1458ae6e37ce6d00998f37e9aba426d5ab80d32ef2d75da4e0
SHA512 (pip-26.1.2.tar.gz) = e29c98a7da5e329183b7eef86a66f9d6c3473051f64aa6e762714306148547eb0de4220824484071822a9a62bd01a62a09ab16bba4c26e4b847bfc2609728608
SHA512 (coverage-0-py3-none-any.whl) = e734192565347010efe68f8ba600254259c9b647f3c553fd4e5d87b1d7f955cb15d6f7d807716f4a6415d239beed945fbec7210feaf502e9cc849c332845926e

View file

@ -0,0 +1,51 @@
From b3d543d7e16af844394316360ef1bf0b9d10f1b1 Mon Sep 17 00:00:00 2001
From: Illia Volochii <illia.volochii@gmail.com>
Date: Wed, 18 Jun 2025 16:25:01 +0300
Subject: [PATCH] Security fix for CVE-2025-50181
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
Co-authored-by: Quentin Pradet <quentin.pradet@gmail.com>
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
---
src/pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py
index fb51bf7..a8de7c6 100644
--- a/src/pip/_vendor/urllib3/poolmanager.py
+++ b/src/pip/_vendor/urllib3/poolmanager.py
@@ -170,6 +170,22 @@ class PoolManager(RequestMethods):
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
RequestMethods.__init__(self, headers)
+ if "retries" in connection_pool_kw:
+ retries = connection_pool_kw["retries"]
+ if not isinstance(retries, Retry):
+ # When Retry is initialized, raise_on_redirect is based
+ # on a redirect boolean value.
+ # But requests made via a pool manager always set
+ # redirect to False, and raise_on_redirect always ends
+ # up being False consequently.
+ # Here we fix the issue by setting raise_on_redirect to
+ # a value needed by the pool manager without considering
+ # the redirect boolean.
+ raise_on_redirect = retries is not False
+ retries = Retry.from_int(retries, redirect=False)
+ retries.raise_on_redirect = raise_on_redirect
+ connection_pool_kw = connection_pool_kw.copy()
+ connection_pool_kw["retries"] = retries
self.connection_pool_kw = connection_pool_kw
self.pools = RecentlyUsedContainer(num_pools)
@@ -389,7 +405,7 @@ class PoolManager(RequestMethods):
kw["body"] = None
kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
- retries = kw.get("retries")
+ retries = kw.get("retries", response.retries)
if not isinstance(retries, Retry):
retries = Retry.from_int(retries, redirect=redirect)
--
2.51.0