diff --git a/SPECS/python-pip/CVE-2026-6357.patch b/SPECS/python-pip/CVE-2026-6357.patch new file mode 100644 index 00000000000..b6846532b16 --- /dev/null +++ b/SPECS/python-pip/CVE-2026-6357.patch @@ -0,0 +1,691 @@ +From 8786d084768e0b42afa093083c8c05ee8e525574 Mon Sep 17 00:00:00 2001 +From: Damian Shaw +Date: Fri, 17 Apr 2026 09:52:38 -0400 +Subject: [PATCH 1/3] Split the pip self-version check to get info before run + +Upstream Patch reference: https://patch-diff.githubusercontent.com/raw/pypa/pip/pull/13923.patch +--- + news/13923.trivial.rst | 2 + + src/pip/_internal/cli/base_command.py | 10 +- + src/pip/_internal/cli/index_command.py | 34 +++++- + src/pip/_internal/commands/install.py | 22 ++++ + src/pip/_internal/commands/list.py | 12 +- + src/pip/_internal/self_outdated_check.py | 75 ++++++------- + tests/unit/test_base_command.py | 8 +- + tests/unit/test_commands.py | 44 ++++++-- + tests/unit/test_self_check_outdated.py | 133 ++++++++++++++++++----- + 9 files changed, 250 insertions(+), 90 deletions(-) + create mode 100644 news/13923.trivial.rst + +diff --git a/news/13923.trivial.rst b/news/13923.trivial.rst +new file mode 100644 +index 0000000..cb3b0ff +--- /dev/null ++++ b/news/13923.trivial.rst +@@ -0,0 +1,2 @@ ++Split the pip self-version check into a fetch phase before the command ++body and an emit phase afterwards. +diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py +index bc1ab65..5da10c0 100644 +--- a/src/pip/_internal/cli/base_command.py ++++ b/src/pip/_internal/cli/base_command.py +@@ -1,11 +1,13 @@ + """Base Command class, and related routines""" + ++import contextlib + import logging + import logging.config + import optparse + import os + import sys + import traceback ++from collections.abc import Iterator + from optparse import Values + from typing import List, Optional, Tuple + +@@ -78,7 +80,8 @@ class Command(CommandContextMixIn): + def add_options(self) -> None: + pass + +- def handle_pip_version_check(self, options: Values) -> None: ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: + """ + This is a no-op so that commands by default do not do the pip version + check. +@@ -86,16 +89,15 @@ class Command(CommandContextMixIn): + # Make sure we do the pip version check if the index_group options + # are present. + assert not hasattr(options, "no_index") ++ yield + + def run(self, options: Values, args: List[str]) -> int: + raise NotImplementedError + + def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int: + def _inner_run() -> int: +- try: ++ with self.pip_version_check(options, args): + return self.run(options, args) +- finally: +- self.handle_pip_version_check(options) + + if options.debug_mode: + rich_traceback.install(show_locals=True) +diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py +index 226f8da..635b9ad 100644 +--- a/src/pip/_internal/cli/index_command.py ++++ b/src/pip/_internal/cli/index_command.py +@@ -6,8 +6,10 @@ so commands which don't always hit the network (e.g. list w/o --outdated or + --uptodate) don't need waste time importing PipSession and friends. + """ + ++import contextlib + import logging + import os ++from collections.abc import Iterator + import sys + from optparse import Values + from typing import TYPE_CHECKING, List, Optional +@@ -21,6 +23,7 @@ if TYPE_CHECKING: + from ssl import SSLContext + + from pip._internal.network.session import PipSession ++ from pip._internal.self_outdated_check import UpgradePrompt + + logger = logging.getLogger(__name__) + +@@ -131,10 +134,18 @@ class SessionCommandMixin(CommandContextMixIn): + return session + + +-def _pip_self_version_check(session: "PipSession", options: Values) -> None: +- from pip._internal.self_outdated_check import pip_self_version_check as check ++def _pip_self_version_check_fetch( ++ session: PipSession, options: Values ++) -> UpgradePrompt | None: ++ from pip._internal.self_outdated_check import pip_self_version_check_fetch + +- check(session, options) ++ return pip_self_version_check_fetch(session, options) ++ ++ ++def _pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: ++ from pip._internal.self_outdated_check import pip_self_version_check_emit ++ ++ pip_self_version_check_emit(upgrade_prompt) + + + class IndexGroupCommand(Command, SessionCommandMixin): +@@ -144,7 +155,8 @@ class IndexGroupCommand(Command, SessionCommandMixin): + This also corresponds to the commands that permit the pip version check. + """ + +- def handle_pip_version_check(self, options: Values) -> None: ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: + """ + Do the pip version check if not disabled. + +@@ -154,17 +166,27 @@ class IndexGroupCommand(Command, SessionCommandMixin): + assert hasattr(options, "no_index") + + if options.disable_pip_version_check or options.no_index: ++ yield + return + ++ upgrade_prompt: UpgradePrompt | None = None + try: +- # Otherwise, check if we're using the latest version of pip available. + session = self._build_session( + options, + retries=0, + timeout=min(5, options.timeout), + ) + with session: +- _pip_self_version_check(session, options) ++ upgrade_prompt = _pip_self_version_check_fetch(session, options) + except Exception: + logger.warning("There was an error checking the latest version of pip.") + logger.debug("See below for error", exc_info=True) ++ ++ try: ++ yield ++ finally: ++ try: ++ _pip_self_version_check_emit(upgrade_prompt) ++ except Exception: ++ logger.warning("There was an error checking the latest version of pip.") ++ logger.debug("See below for error", exc_info=True) +diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py +index ad45a2f..02bc885 100644 +--- a/src/pip/_internal/commands/install.py ++++ b/src/pip/_internal/commands/install.py +@@ -1,12 +1,15 @@ ++import contextlib + import errno + import json + import operator + import os + import shutil + import site ++from collections.abc import Iterator + from optparse import SUPPRESS_HELP, Values + from typing import List, Optional + ++from pip._vendor.packaging.requirements import InvalidRequirement, Requirement + from pip._vendor.packaging.utils import canonicalize_name + from pip._vendor.rich import print_json + +@@ -50,6 +53,14 @@ from pip._internal.wheel_builder import build, should_build_for_install_command + logger = getLogger(__name__) + + ++def _arg_refers_to_pip(arg: str) -> bool: ++ try: ++ req = Requirement(arg) ++ except InvalidRequirement: ++ return False ++ return canonicalize_name(req.name) == "pip" ++ ++ + class InstallCommand(RequirementCommand): + """ + Install packages from: +@@ -263,6 +274,17 @@ class InstallCommand(RequirementCommand): + ), + ) + ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: ++ # Skip the self-version check when pip itself is a requirement. The ++ # running pip may be replaced mid-command, and the upgrade prompt ++ # is redundant. ++ if any(_arg_refers_to_pip(arg) for arg in args): ++ yield ++ return ++ with super().pip_version_check(options, args): ++ yield ++ + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + if options.use_user_site and options.target_dir is not None: +diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py +index 82fc46a..b850e32 100644 +--- a/src/pip/_internal/commands/list.py ++++ b/src/pip/_internal/commands/list.py +@@ -1,5 +1,7 @@ ++import contextlib + import json + import logging ++from collections.abc import Iterator + from optparse import Values + from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast + +@@ -134,9 +136,13 @@ class ListCommand(IndexGroupCommand): + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + +- def handle_pip_version_check(self, options: Values) -> None: +- if options.outdated or options.uptodate: +- super().handle_pip_version_check(options) ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: ++ if not (options.outdated or options.uptodate): ++ yield ++ return ++ with super().pip_version_check(options, args): ++ yield + + def _build_package_finder( + self, options: Values, session: "PipSession" +diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py +index f9a91af..5ec9df1 100644 +--- a/src/pip/_internal/self_outdated_check.py ++++ b/src/pip/_internal/self_outdated_check.py +@@ -1,5 +1,4 @@ + import datetime +-import functools + import hashlib + import json + import logging +@@ -7,7 +6,7 @@ import optparse + import os.path + import sys + from dataclasses import dataclass +-from typing import Any, Callable, Dict, Optional ++from typing import Any, Dict, Optional + + from pip._vendor.packaging.version import Version + from pip._vendor.packaging.version import parse as parse_version +@@ -26,7 +25,8 @@ from pip._internal.utils.entrypoints import ( + get_best_invocation_for_this_python, + ) + from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace +-from pip._internal.utils.misc import ensure_dir ++from pip._internal.utils.misc import ensure_dir, check_externally_managed ++from pip._internal.exceptions import ExternallyManagedEnvironment + + _WEEK = datetime.timedelta(days=7) + +@@ -149,16 +149,6 @@ class UpgradePrompt: + ) + + +-def was_installed_by_pip(pkg: str) -> bool: +- """Checks whether pkg was installed by pip +- +- This is used not to display the upgrade message when pip is in fact +- installed by system package manager, such as dnf on Fedora. +- """ +- dist = get_default_environment().get_distribution(pkg) +- return dist is not None and "pip" == dist.installer +- +- + def _get_current_remote_pip_version( + session: PipSession, options: optparse.Values + ) -> Optional[str]: +@@ -187,28 +177,15 @@ def _get_current_remote_pip_version( + return str(best_candidate.version) + + +-def _self_version_check_logic( +- *, +- state: SelfCheckState, +- current_time: datetime.datetime, +- local_version: Version, +- get_remote_version: Callable[[], Optional[str]], ++def _compute_upgrade_prompt( ++ local_version: Version, remote_version_str: str, installed_by_pip: bool + ) -> Optional[UpgradePrompt]: +- remote_version_str = state.get(current_time) +- if remote_version_str is None: +- remote_version_str = get_remote_version() +- if remote_version_str is None: +- logger.debug("No remote pip version found") +- return None +- state.set(remote_version_str, current_time) +- + remote_version = parse_version(remote_version_str) + logger.debug("Remote version of pip: %s", remote_version) + logger.debug("Local version of pip: %s", local_version) ++ logger.debug("Was pip installed by pip? %s", installed_by_pip) + +- pip_installed_by_pip = was_installed_by_pip("pip") +- logger.debug("Was pip installed by pip? %s", pip_installed_by_pip) +- if not pip_installed_by_pip: ++ if not installed_by_pip: + return None # Only suggest upgrade if pip is installed by pip. + + local_version_is_older = ( +@@ -221,24 +198,44 @@ def _self_version_check_logic( + return None + + +-def pip_self_version_check(session: PipSession, options: optparse.Values) -> None: +- """Check for an update for pip. ++def pip_self_version_check_fetch( ++ session: PipSession, options: optparse.Values ++) -> UpgradePrompt | None: ++ """Compute the pip upgrade prompt, if any, before the command runs. + + Limit the frequency of checks to once per week. State is stored either in + the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix + of the pip script path. ++ ++ Pair with :func:`pip_self_version_check_emit`, which displays the prompt ++ after the command body runs. + """ + installed_dist = get_default_environment().get_distribution("pip") + if not installed_dist: +- return ++ return None ++ try: ++ check_externally_managed() ++ except ExternallyManagedEnvironment: ++ return None ++ ++ state = SelfCheckState(cache_dir=options.cache_dir) ++ current_time = datetime.datetime.now(datetime.timezone.utc) ++ remote_version_str = state.get(current_time) ++ if remote_version_str is None: ++ remote_version_str = _get_current_remote_pip_version(session, options) ++ if remote_version_str is None: ++ logger.debug("No remote pip version found") ++ return None ++ state.set(remote_version_str, current_time) + +- upgrade_prompt = _self_version_check_logic( +- state=SelfCheckState(cache_dir=options.cache_dir), +- current_time=datetime.datetime.now(datetime.timezone.utc), ++ return _compute_upgrade_prompt( + local_version=installed_dist.version, +- get_remote_version=functools.partial( +- _get_current_remote_pip_version, session, options +- ), ++ remote_version_str=remote_version_str, ++ installed_by_pip=installed_dist.installer == "pip", + ) ++ ++ ++def pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: ++ """Emit the upgrade prompt captured by :func:`pip_self_version_check_fetch`.""" + if upgrade_prompt is not None: + logger.warning("%s", upgrade_prompt, extra={"rich": True}) +diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py +index f9fae65..5fdf94a 100644 +--- a/tests/unit/test_base_command.py ++++ b/tests/unit/test_base_command.py +@@ -97,14 +97,14 @@ class TestCommand: + assert "Traceback (most recent call last):" in stderr + + +-@patch("pip._internal.cli.index_command.Command.handle_pip_version_check") +-def test_handle_pip_version_check_called(mock_handle_version_check: Mock) -> None: ++@patch("pip._internal.cli.index_command.Command.pip_version_check") ++def test_pip_version_check_called(mock_version_check: Mock) -> None: + """ +- Check that Command.handle_pip_version_check() is called. ++ Check that ``Command.pip_version_check()`` wraps the command body. + """ + cmd = FakeCommand() + cmd.main([]) +- mock_handle_version_check.assert_called_once() ++ mock_version_check.assert_called_once() + + + def test_log_command_success(fixed_time: None, tmpdir: Path) -> None: +diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py +index 9d5aefe..095faf4 100644 +--- a/tests/unit/test_commands.py ++++ b/tests/unit/test_commands.py +@@ -79,16 +79,16 @@ def test_index_group_commands() -> None: + @pytest.mark.parametrize( + "disable_pip_version_check, no_index, expected_called", + [ +- # pip_self_version_check() is only called when both +- # disable_pip_version_check and no_index are False. ++ # The fetch phase only runs when both disable_pip_version_check ++ # and no_index are False. + (False, False, True), + (False, True, False), + (True, False, False), + (True, True, False), + ], + ) +-@mock.patch("pip._internal.cli.index_command._pip_self_version_check") +-def test_index_group_handle_pip_version_check( ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") ++def test_index_group_pip_version_check( + mock_version_check: mock.Mock, + command_name: str, + disable_pip_version_check: bool, +@@ -96,26 +96,52 @@ def test_index_group_handle_pip_version_check( + expected_called: bool, + ) -> None: + """ +- Test whether pip_self_version_check() is called when +- handle_pip_version_check() is called, for each of the +- IndexGroupCommand classes. ++ Test whether the pre-body fetch runs when ``pip_version_check()`` is ++ entered, for each of the IndexGroupCommand classes. + """ + command = create_command(command_name) + options = command.parser.get_default_values() + options.disable_pip_version_check = disable_pip_version_check + options.no_index = no_index ++ # Return None so the emit branch is a no-op. ++ mock_version_check.return_value = None + + # See test test_list_pip_version_check() below. + if command_name == "list": + expected_called = False + +- command.handle_pip_version_check(options) ++ with command.pip_version_check(options, []): ++ pass + if expected_called: + mock_version_check.assert_called_once() + else: + mock_version_check.assert_not_called() + + ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") ++def test_install_pip_version_check_skipped_when_pip_is_a_requirement( ++ mock_version_check: mock.Mock, ++) -> None: ++ """``pip install pip`` must skip the self-version check: the running pip ++ may be replaced before emit.""" ++ command = create_command("install") ++ options = command.parser.get_default_values() ++ options.disable_pip_version_check = False ++ options.no_index = False ++ ++ with command.pip_version_check(options, ["pip"]): ++ pass ++ mock_version_check.assert_not_called() ++ ++ with command.pip_version_check(options, ["pip==25.0"]): ++ pass ++ mock_version_check.assert_not_called() ++ ++ with command.pip_version_check(options, ["some-other-pkg"]): ++ pass ++ mock_version_check.assert_called_once() ++ ++ + def test_requirement_commands() -> None: + """ + Test which commands inherit from RequirementCommand. +@@ -128,7 +154,7 @@ def test_requirement_commands() -> None: + + + @pytest.mark.parametrize("flag", ["", "--outdated", "--uptodate"]) +-@mock.patch("pip._internal.cli.index_command._pip_self_version_check") ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") + @mock.patch.dict(os.environ, {"PIP_DISABLE_PIP_VERSION_CHECK": "no"}) + def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> None: + """ +diff --git a/tests/unit/test_self_check_outdated.py b/tests/unit/test_self_check_outdated.py +index 99a556e..b62da75 100644 +--- a/tests/unit/test_self_check_outdated.py ++++ b/tests/unit/test_self_check_outdated.py +@@ -6,13 +6,26 @@ import sys + from optparse import Values + from pathlib import Path + from typing import Optional +-from unittest.mock import ANY, Mock, patch ++from unittest.mock import Mock, patch + + import pytest + from freezegun import freeze_time + from pip._vendor.packaging.version import Version + + from pip._internal import self_outdated_check ++from pip._internal.self_outdated_check import ( ++ UpgradePrompt, ++ pip_self_version_check_emit, ++ pip_self_version_check_fetch, ++) ++ ++ ++def _make_installed_dist(version: str, installer: str = "pip") -> Mock: ++ """Build a stand-in for the installed pip distribution.""" ++ installed_dist = Mock() ++ installed_dist.version = Version(version) ++ installed_dist.installer = installer ++ return installed_dist + + + @pytest.mark.parametrize( +@@ -33,31 +46,59 @@ def test_get_statefile_name_known_values(key: str, expected: str) -> None: + + + @freeze_time("1970-01-02T11:00:00Z") +-@patch("pip._internal.self_outdated_check._self_version_check_logic") ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") + @patch("pip._internal.self_outdated_check.SelfCheckState") +-def test_pip_self_version_check_calls_underlying_implementation( +- mocked_state: Mock, mocked_function: Mock, tmpdir: Path ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_pip_self_version_check_fetch_calls_underlying_implementation( ++ mocked_env: Mock, mocked_state: Mock, mocked_get_remote: Mock, tmpdir: Path + ) -> None: + # GIVEN + mock_session = Mock() + fake_options = Values({"cache_dir": str(tmpdir)}) +- mocked_function.return_value = None ++ mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") ++ mocked_state.return_value.get.return_value = None ++ mocked_get_remote.return_value = "5.0" + + # WHEN +- self_outdated_check.pip_self_version_check(mock_session, fake_options) ++ result = pip_self_version_check_fetch(mock_session, fake_options) + + # THEN ++ assert result == UpgradePrompt(old="1.0", new="5.0") + mocked_state.assert_called_once_with(cache_dir=str(tmpdir)) +- mocked_function.assert_called_once_with( +- state=mocked_state(cache_dir=str(tmpdir)), +- current_time=datetime.datetime( +- 1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc +- ), +- local_version=ANY, +- get_remote_version=ANY, ++ mocked_get_remote.assert_called_once_with(mock_session, fake_options) ++ mocked_state.return_value.set.assert_called_once_with( ++ "5.0", ++ datetime.datetime(1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc), + ) + + ++def test_pip_self_version_check_emit_logs_prompt( ++ caplog: pytest.LogCaptureFixture, ++) -> None: ++ # GIVEN ++ prompt = UpgradePrompt(old="1.0", new="2.0") ++ ++ # WHEN ++ with caplog.at_level(logging.WARNING): ++ pip_self_version_check_emit(prompt) ++ ++ # THEN ++ assert len(caplog.records) == 1 ++ assert caplog.records[0].levelno == logging.WARNING ++ ++ ++def test_pip_self_version_check_emit_no_prompt_is_silent( ++ caplog: pytest.LogCaptureFixture, ++) -> None: ++ # WHEN ++ with caplog.at_level(logging.WARNING): ++ pip_self_version_check_emit(None) ++ ++ # THEN ++ assert caplog.records == [] ++ ++ ++@freeze_time("2000-01-01T00:00:00Z") + @pytest.mark.parametrize( + [ + "installed_version", +@@ -89,27 +130,41 @@ def test_core_logic( + should_show_prompt: bool, + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, ++ tmpdir: Path, + ) -> None: + # GIVEN ++ installed_dist = _make_installed_dist( ++ installed_version, installer="pip" if installed_by_pip else "apt" ++ ) ++ monkeypatch.setattr( ++ self_outdated_check, ++ "get_default_environment", ++ lambda: Mock(get_distribution=Mock(return_value=installed_dist)), ++ ) ++ monkeypatch.setattr(self_outdated_check, "check_externally_managed", lambda: None) ++ monkeypatch.setattr( ++ self_outdated_check, ++ "_get_current_remote_pip_version", ++ lambda session, options: remote_version, ++ ) ++ mock_state_instance = Mock() ++ mock_state_instance.get.return_value = stored_version + monkeypatch.setattr( +- self_outdated_check, "was_installed_by_pip", lambda _: installed_by_pip ++ self_outdated_check, ++ "SelfCheckState", ++ Mock(return_value=mock_state_instance), + ) +- mock_state = Mock() +- mock_state.get.return_value = stored_version +- fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0) ++ fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) + version_that_should_be_checked = stored_version or remote_version + + # WHEN + with caplog.at_level(logging.DEBUG): +- return_value = self_outdated_check._self_version_check_logic( +- state=mock_state, +- current_time=fake_time, +- local_version=Version(installed_version), +- get_remote_version=lambda: remote_version, ++ return_value = pip_self_version_check_fetch( ++ session=Mock(), options=Values({"cache_dir": str(tmpdir)}) + ) + + # THEN +- mock_state.get.assert_called_once_with(fake_time) ++ mock_state_instance.get.assert_called_once_with(fake_time) + assert caplog.messages == [ + f"Remote version of pip: {version_that_should_be_checked}", + f"Local version of pip: {installed_version}", +@@ -117,9 +172,9 @@ def test_core_logic( + ] + + if stored_version: +- mock_state.set.assert_not_called() ++ mock_state_instance.set.assert_not_called() + else: +- mock_state.set.assert_called_once_with( ++ mock_state_instance.set.assert_called_once_with( + version_that_should_be_checked, fake_time + ) + +@@ -184,3 +239,31 @@ class TestSelfCheckState: + "last_check": "2000-01-01T00:00:00+00:00", + "pypi_version": "1.0.0", + } ++ ++ ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_fetch_suppressed_by_externally_managed( ++ mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path ++) -> None: ++ mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") ++ fake_options = Values({"cache_dir": str(tmpdir)}) ++ with patch( ++ "pip._internal.self_outdated_check.check_externally_managed", ++ side_effect=ExternallyManagedEnvironment("nope"), ++ ): ++ result = pip_self_version_check_fetch(session=Mock(), options=fake_options) ++ assert result is None ++ mocked_get_remote.assert_not_called() ++ ++ ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_fetch_skipped_when_pip_not_installed( ++ mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path ++) -> None: ++ mocked_env.return_value.get_distribution.return_value = None ++ fake_options = Values({"cache_dir": str(tmpdir)}) ++ result = pip_self_version_check_fetch(session=Mock(), options=fake_options) ++ assert result is None ++ mocked_get_remote.assert_not_called() +-- +2.43.0 + diff --git a/SPECS/python-pip/python-pip.spec b/SPECS/python-pip/python-pip.spec index a50fc18f729..e2049bc7f37 100644 --- a/SPECS/python-pip/python-pip.spec +++ b/SPECS/python-pip/python-pip.spec @@ -5,7 +5,7 @@ A tool for installing and managing Python packages} Summary: A tool for installing and managing Python packages Name: python-pip Version: 24.2 -Release: 7%{?dist} +Release: 8%{?dist} License: MIT AND Python-2.0.1 AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND LGPL-2.1-only AND MPL-2.0 AND (Apache-2.0 OR BSD-2-Clause) Vendor: Microsoft Corporation Distribution: Azure Linux @@ -17,6 +17,7 @@ Patch1: CVE-2025-8869.patch Patch2: CVE-2025-50181.patch Patch3: CVE-2026-1703.patch Patch4: CVE-2026-3219.patch +Patch5: CVE-2026-6357.patch BuildArch: noarch @@ -60,6 +61,9 @@ BuildRequires: python3-wheel %{python3_sitelib}/pip* %changelog +* Fri May 08 2026 Azure Linux Security Servicing Account - 24.2-8 +- Patch for CVE-2026-6357 + * Wed Apr 22 2026 Azure Linux Security Servicing Account - 24.2-7 - Patch for CVE-2026-3219 diff --git a/toolkit/resources/manifests/package/toolchain_aarch64.txt b/toolkit/resources/manifests/package/toolchain_aarch64.txt index 75e56ccf6f2..74109d3d8fa 100644 --- a/toolkit/resources/manifests/package/toolchain_aarch64.txt +++ b/toolkit/resources/manifests/package/toolchain_aarch64.txt @@ -550,7 +550,7 @@ python3-magic-5.45-1.azl3.noarch.rpm python3-markupsafe-2.1.3-1.azl3.aarch64.rpm python3-newt-0.52.23-1.azl3.aarch64.rpm python3-packaging-23.2-3.azl3.noarch.rpm -python3-pip-24.2-7.azl3.noarch.rpm +python3-pip-24.2-8.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.aarch64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm diff --git a/toolkit/resources/manifests/package/toolchain_x86_64.txt b/toolkit/resources/manifests/package/toolchain_x86_64.txt index bff83e56322..be9ed184f9e 100644 --- a/toolkit/resources/manifests/package/toolchain_x86_64.txt +++ b/toolkit/resources/manifests/package/toolchain_x86_64.txt @@ -558,7 +558,7 @@ python3-magic-5.45-1.azl3.noarch.rpm python3-markupsafe-2.1.3-1.azl3.x86_64.rpm python3-newt-0.52.23-1.azl3.x86_64.rpm python3-packaging-23.2-3.azl3.noarch.rpm -python3-pip-24.2-7.azl3.noarch.rpm +python3-pip-24.2-8.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.x86_64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm