From 2ecdab3b6862438271cebe4af253962a9887a0da Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:20:47 -0500 Subject: [PATCH 01/12] Backport CVE-2026-3219: disambiguate tar/zip archive detection in unpack_file GHSA-58qw-9mgm-455v. unpack_file() treated any file that zipfile.is_zipfile() accepted as a zip, regardless of its name or whether it was also a valid tar. A tar+zip polyglot could therefore install different contents from what its .tar.gz name suggests. Choose the format from the content type, then the file extension, then the magic signature, and refuse to unpack a file whose signature matches both formats. Upstream: pypa/pip#13870 (c4cdbf9b41e), released in 26.1. Source change cherry-picked unchanged; the test import block was resolved against 26.0.1, which lacks the _get_default_mode_plus_executable import the upstream test file had picked up in the meantime (unused by these tests). Co-Authored-By: Claude Opus 5.5 --- news/13867.bugfix.rst | 1 + src/pip/_internal/utils/unpacking.py | 63 +++++--- tests/unit/test_utils_unpacking.py | 211 ++++++++++++++++++++++++++- 3 files changed, 252 insertions(+), 23 deletions(-) create mode 100644 news/13867.bugfix.rst diff --git a/news/13867.bugfix.rst b/news/13867.bugfix.rst new file mode 100644 index 0000000000..4c68ae9f88 --- /dev/null +++ b/news/13867.bugfix.rst @@ -0,0 +1 @@ +Fix installing ``.tar.gz`` source distributions that look like a zip file. diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index b3f52e85e6..879b40c37e 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -336,27 +336,46 @@ def unpack_file( location: str, content_type: str | None = None, ) -> None: + """Unpack ``filename`` into ``location``. + + Archive format is chosen in order of decreasing reliability: + ``content_type``, then filename extension, then magic signature + (unambiguous matches only). + """ filename = os.path.realpath(filename) - if ( - content_type == "application/zip" - or filename.lower().endswith(ZIP_EXTENSIONS) - or zipfile.is_zipfile(filename) - ): - unzip_file(filename, location, flatten=not filename.endswith(".whl")) - elif ( - content_type == "application/x-gzip" - or tarfile.is_tarfile(filename) - or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS) - ): + zip_flatten = not filename.endswith(".whl") + + def _unzip() -> None: + unzip_file(filename, location, flatten=zip_flatten) + + def _untar() -> None: untar_file(filename, location) - else: - # FIXME: handle? - # FIXME: magic signatures? - logger.critical( - "Cannot unpack file %s (downloaded from %s, content-type: %s); " - "cannot detect archive format", - filename, - location, - content_type, - ) - raise InstallationError(f"Cannot determine archive format of {location}") + + if content_type == "application/zip": + return _unzip() + if content_type == "application/x-gzip": + return _untar() + + if filename.lower().endswith(ZIP_EXTENSIONS): + return _unzip() + if filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS): + return _untar() + + # avoid ambiguous case where both signature checks return True + is_zipfile = zipfile.is_zipfile(filename) + is_tarfile = tarfile.is_tarfile(filename) + if is_zipfile and not is_tarfile: + return _unzip() + if is_tarfile and not is_zipfile: + return _untar() + if is_zipfile and is_tarfile: + logger.error("Ambiguous file signature in %s.", filename) + + logger.critical( + "Cannot unpack file %s (downloaded from %s, content-type: %s); " + "cannot detect archive format", + filename, + location, + content_type, + ) + raise InstallationError(f"Cannot determine archive format of {location}") diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 582da9722b..113c6df8c4 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import os import shutil @@ -8,12 +10,18 @@ import time import zipfile from pathlib import Path +from unittest.mock import MagicMock, patch import pytest from _pytest.monkeypatch import MonkeyPatch from pip._internal.exceptions import InstallationError -from pip._internal.utils.unpacking import is_within_directory, untar_file, unzip_file +from pip._internal.utils.unpacking import ( + is_within_directory, + unpack_file, + untar_file, + unzip_file, +) from tests.lib import TestData @@ -419,3 +427,204 @@ def test_unpack_tar_unicode(tmpdir: Path) -> None: def test_is_within_directory(args: tuple[str, str], expected: bool) -> None: result = is_within_directory(*args) assert result == expected + + +@pytest.mark.parametrize( + "is_zip, is_tar, unzip, untar, exception", + [ + # zip file + (True, False, True, False, False), + # tar file + (False, True, False, True, False), + # neither zip nor tar + (False, False, False, False, True), + # ambiguous (both zip and tar) + (True, True, False, False, True), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_magic_signature_check_logic( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + is_zip: bool, + is_tar: bool, + unzip: bool, + untar: bool, + exception: bool, +) -> None: + """ + Test that pip throws an error if file is identified as both zip and tar + and all other checks came out undeterministic. + """ + mock_tarfile.is_tarfile.return_value = is_tar + mock_zipfile.is_zipfile.return_value = is_zip + filename = "ambiguous-file.unknown-extension" + + if exception: + with pytest.raises(InstallationError): + unpack_file(filename, "any-location", content_type=None) + else: + unpack_file(filename, "any-location", content_type=None) + + mock_unzip.assert_called_once() if unzip else mock_unzip.assert_not_called() + mock_untar.assert_called_once() if untar else mock_untar.assert_not_called() + mock_tarfile.is_tarfile.assert_called_once() + mock_zipfile.is_zipfile.assert_called_once() + + +@pytest.mark.parametrize( + "filename, content_type, unzip, untar", + [ + # content_type check + ("noname", "application/zip", True, False), + ("noname", "application/x-gzip", False, True), + # filename check + ("ok.zip", None, True, False), + ("ok.tar.gz", None, False, True), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_check_priority( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + filename: str, + content_type: str | None, + unzip: bool, + untar: bool, +) -> None: + """ + Test the order of priority of checks to ensure + we don't use magic signature check unless we have to. + """ + unpack_file(filename, "any-location", content_type=content_type) + mock_unzip.assert_called_once() if unzip else mock_unzip.assert_not_called() + mock_untar.assert_called_once() if untar else mock_untar.assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize( + "filename, expect_unzip", + [ + ("pkg.zip", True), + ("pkg.ZIP", True), + ("pkg-1.0-py3-none-any.whl", True), + ("pkg.tar.gz", False), + ("pkg.TAR.GZ", False), + ("pkg.tgz", False), + ("pkg.tar", False), + ("pkg.tar.bz2", False), + ("pkg.tbz", False), + ("pkg.tar.xz", False), + ("pkg.txz", False), + ("pkg.tlz", False), + ("pkg.tar.lz", False), + ("pkg.tar.lzma", False), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_filename_extension_routing( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + filename: str, + expect_unzip: bool, +) -> None: + unpack_file(filename, "any-location", content_type=None) + (mock_unzip if expect_unzip else mock_untar).assert_called_once() + (mock_untar if expect_unzip else mock_unzip).assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize( + "content_type, filename, expect_unzip", + [ + ("application/zip", "pkg.tar.gz", True), + ("application/x-gzip", "pkg.zip", False), + ("application/x-gzip", "pkg.whl", False), + ("application/octet-stream", "pkg.zip", True), + ("application/octet-stream", "pkg.tar.gz", False), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_content_type_vs_filename_priority( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + content_type: str, + filename: str, + expect_unzip: bool, +) -> None: + unpack_file(filename, "any-location", content_type=content_type) + (mock_unzip if expect_unzip else mock_untar).assert_called_once() + (mock_untar if expect_unzip else mock_unzip).assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize("filename, flatten", [("pkg.whl", False), ("pkg.zip", True)]) +@patch("pip._internal.utils.unpacking.unzip_file") +def test_flatten_only_for_non_whl( + mock_unzip: MagicMock, filename: str, flatten: bool +) -> None: + unpack_file(filename, "any-location", content_type=None) + assert mock_unzip.call_args.kwargs["flatten"] is flatten + + +def _write_polyglot(path: Path) -> None: + """Write a tar.gz with a zip appended; both views contain payload.txt.""" + tar_buf = io.BytesIO() + with tarfile.open(fileobj=tar_buf, mode="w:gz") as tar: + info = tarfile.TarInfo("pkg/payload.txt") + info.size = 8 + tar.addfile(info, io.BytesIO(b"from-tar")) + zip_buf = io.BytesIO() + with zipfile.ZipFile(zip_buf, "w") as zf: + zf.writestr("pkg/payload.txt", "from-zip") + path.write_bytes(tar_buf.getvalue() + zip_buf.getvalue()) + + +@pytest.mark.parametrize( + "filename, content_type, expected", + [ + ("pkg.tar.gz", None, b"from-tar"), + ("pkg.tgz", None, b"from-tar"), + ("pkg.zip", None, b"from-zip"), + ("pkg.tar.gz", "application/zip", b"from-zip"), + ("pkg.unknown", "application/x-gzip", b"from-tar"), + ], +) +def test_polyglot_routing( + tmp_path: Path, filename: str, content_type: str | None, expected: bytes +) -> None: + archive = tmp_path / filename + _write_polyglot(archive) + out = tmp_path / "out" + unpack_file(str(archive), str(out), content_type=content_type) + assert (out / "payload.txt").read_bytes() == expected + + +def test_polyglot_ambiguous_name_rejected(tmp_path: Path) -> None: + archive = tmp_path / "pkg.bin" + _write_polyglot(archive) + with pytest.raises(InstallationError): + unpack_file(str(archive), str(tmp_path / "out")) From 744cc6aca8c5f14df58df54f3716b27cfd050737 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:20:47 -0500 Subject: [PATCH 02/12] Backport CVE-2026-8643: reject entry point names that escape the scripts dir GHSA-wf93-45jw-7689. pip joined console_scripts / gui_scripts entry point names onto the scripts directory without checking that the result stays inside it. The vendored distlib 0.4.0 accepts any non-whitespace name (ENTRY_RE name group [^\[]\S*) and its ScriptMaker has no directory guard, so a wheel declaring e.g. '../../outside = pkg:main' or an absolute path wrote a script outside the scripts directory on 26.0.1. Add _script_within_dir() and have _raise_for_invalid_entrypoint() (called from PipScriptMaker.make with self.target_dir) raise InstallationError for any name that resolves outside the scripts dir. Upstream: pypa/pip#14000 (8eb178480bd), released in 26.1.2. Cherry-picked unchanged, including tests and news fragment. Co-Authored-By: Claude Opus 5.5 --- news/14000.bugfix.rst | 2 + src/pip/_internal/operations/install/wheel.py | 26 +++++++- tests/unit/test_wheel.py | 64 +++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 news/14000.bugfix.rst diff --git a/news/14000.bugfix.rst b/news/14000.bugfix.rst new file mode 100644 index 0000000000..3b86f1b3b0 --- /dev/null +++ b/news/14000.bugfix.rst @@ -0,0 +1,2 @@ +Reject ``console_scripts`` and ``gui_scripts`` entry points whose name would +install a script outside the scripts directory. diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 40097d6a79..231e400658 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -397,11 +397,31 @@ def __init__(self, entry_point: str) -> None: ) -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 e0ac6497b7..c1aafe5325 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -462,6 +462,32 @@ def test_invalid_entrypoints_fail( 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 From 3f09648bed5c03730a2def54bf83fec4126dced8 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:20:56 -0500 Subject: [PATCH 03/12] Backport CVE-2026-13346: stop Link.filename decoding the URL path twice GHSA-qwm4-qh6w-59xr. Link already percent-decodes the URL path into self._path, but Link.filename unquoted the basename again, so a doubly-encoded separator from a malicious index (..%252F..%252Fevil.whl) became a real '../../evil.whl' and the download was written outside the download directory. This matters most for `pip download --only-binary`. Drop the second decode, make Link.filename always return a single path component (PathComponent), validate the HTTP response file name with as_path_component(), and route the download-path joins in Downloader, _check_download_dir and RequirementPreparer.save_linked_requirement through join_within_directory(). Upstream: pypa/pip#14110 (10dfb6b9005), released in 26.2.0. Cherry-picked; only the import blocks in download.py and prepare.py conflicted and were resolved against 26.0.1's imports (26.0.1 still imports PyPI in download.py and does not need DirectUrl in prepare.py). Co-Authored-By: Claude Opus 5.5 --- news/14110.bugfix.rst | 1 + src/pip/_internal/models/link.py | 63 ++++++++++--- 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 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 200ec34c56..1a64398733 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -14,6 +14,7 @@ from typing import ( Any, NamedTuple, + NewType, ) from pip._internal.exceptions import InvalidEggFragment @@ -31,6 +32,49 @@ logger = logging.getLogger(__name__) +# A single path component: percent-decoded once and reduced to a basename, so it +# contains no path separator and is not a ``.`` or ``..`` reference. The empty +# string means "no component". +PathComponent = NewType("PathComponent", str) + + +def _to_path_component(name: str) -> PathComponent: + """Reduce ``name`` to a single path component, or ``""`` if it has none. + + ``os.path.basename`` drops any directory part, drive letter, or separator; + a ``.``, ``..``, or empty result is not a component and becomes ``""``. + """ + name = os.path.basename(name) + if name in ("", os.curdir, os.pardir): + return PathComponent("") + + return PathComponent(name) + + +def as_path_component(name: str) -> PathComponent: + """Like ``_to_path_component`` but reject the empty result. + + Use where a file is about to be written, so a missing name is an error + rather than a silent fallback to the directory itself. + """ + component = _to_path_component(name) + if not component: + raise ValueError(f"Unexpected file name derived from URL: {name!r}") + + return component + + +def join_within_directory(directory: str, component: PathComponent) -> str: + """Join a single path ``component`` onto ``directory``. + + ``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) + + # 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") @@ -423,18 +467,13 @@ def redacted_url(self) -> str: return redact_auth_from_url(self.url) @property - def filename(self) -> str: - path = self.path.rstrip("/") - name = posixpath.basename(path) - if not name: - # Make sure we don't leak auth information if the netloc - # includes a username and password. - netloc, user_pass = split_auth_from_netloc(self.netloc) - return netloc - - name = urllib.parse.unquote(name) - assert name, f"URL {self._url!r} produced no filename" - return name + def filename(self) -> PathComponent: + name = _to_path_component(posixpath.basename(self.path.rstrip("/"))) + if name: + return name + + # No component in the path; fall back to the netloc, dropping any auth. + return _to_path_component(split_auth_from_netloc(self.netloc)[0]) @property def file_path(self) -> str: diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 26966423f6..fa71c75a38 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -20,7 +20,12 @@ 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, + PathComponent, + as_path_component, + join_within_directory, +) 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 @@ -117,11 +122,14 @@ def parse_content_disposition(content_disposition: str, default_filename: str) - return filename or default_filename -def _get_http_response_filename(resp: Response, link: Link) -> str: +def _get_http_response_filename(resp: Response, link: Link) -> PathComponent: """Get an ideal filename from the given HTTP response, falling back to the link filename if not provided. + + The result is validated as a single path component, so it can be joined onto + a download directory without escaping it. """ - filename = link.filename # fallback + filename: str = link.filename # fallback # Have a look at the Content-Disposition header for a better guess content_disposition = resp.headers.get("content-disposition") if content_disposition: @@ -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 - return filename + return as_path_component(filename) @dataclass @@ -188,7 +196,9 @@ def __call__(self, link: Link, location: str) -> tuple[str, str]: resp = self._http_get(link) download_size = _get_http_response_size(resp) - filepath = os.path.join(location, _get_http_response_filename(resp, link)) + filepath = join_within_directory( + location, _get_http_response_filename(resp, link) + ) with open(filepath, "wb") as content_file: 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 67f9ee9504..d260d15a28 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -29,7 +29,7 @@ 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 -from pip._internal.models.link import Link +from pip._internal.models.link import Link, join_within_directory from pip._internal.models.wheel import Wheel from pip._internal.network.download import Downloader from pip._internal.network.lazy_wheel import ( @@ -201,7 +201,7 @@ def _check_download_dir( """Check download_dir for previously downloaded file with correct hash If a correct file is found return its path else None """ - download_path = os.path.join(download_dir, link.filename) + download_path = join_within_directory(download_dir, link.filename) if not os.path.exists(download_path): return None @@ -683,7 +683,7 @@ def save_linked_requirement(self, req: InstallRequirement) -> None: # No distribution was downloaded for this requirement. return - download_location = os.path.join(self.download_dir, link.filename) + download_location = join_within_directory(self.download_dir, link.filename) if not os.path.exists(download_location): 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 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -1,9 +1,17 @@ from __future__ import annotations +import os +import posixpath + import pytest from pip._internal.exceptions import InvalidEggFragment, PipError -from pip._internal.models.link import Link, links_equivalent +from pip._internal.models.link import ( + Link, + as_path_component, + join_within_directory, + links_equivalent, +) from pip._internal.utils.hashes import Hashes @@ -29,6 +37,13 @@ def test_repr(self, url: str, expected: str) -> None: ("https://example.com/path/page.html", "page.html"), # Test a quoted character. ("https://example.com/path/page%231.html", "page#1.html"), + # A doubly-encoded separator must stay encoded: the path is decoded + # exactly once, so the file name keeps its literal "%2F" instead of + # collapsing into a "/". + ( + "https://example.com/a%252Fb.whl", + "a%2Fb.whl", + ), ( "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: link = Link(url) assert link.filename == expected + @pytest.mark.parametrize( + "url", + [ + "https://example.com/a%252Fb.whl", + "https://example.com/%252e%252e%252fb.whl", + ], + ) + def test_filename_decoded_once_stays_single_component(self, url: str) -> None: + # The path is decoded exactly once, so an encoded separator stays + # encoded and the file name remains a single path component rather + # than collapsing into a "/"-separated path. + filename = Link(url).filename + assert not posixpath.isabs(filename) + assert posixpath.basename(filename) == filename + + @pytest.mark.parametrize( + "url", + [ + "https://example.com/..", + "https://example.com/.", + "https://example.com/foo/%2e%2e", + ], + ) + def test_filename_parent_reference_falls_back_to_netloc(self, url: str) -> None: + # A path that is only a "." or ".." reference has no usable file name, + # so filename falls back to the netloc rather than handing back a + # traversal component that could escape a download directory. + assert Link(url).filename == "example.com" + + @pytest.mark.parametrize( + "url", + [ + # A path-less URL whose authority looks like a traversal: the netloc + # fallback must still reduce to a single path component. + "http://..\\..\\..\\evil.whl", + "http://../", + "http://..", + ], + ) + def test_filename_is_always_a_path_component(self, url: str) -> None: + # filename must never carry a separator or parent reference, so joining + # it onto a directory can never escape that directory. + name = Link(url).filename + assert os.path.basename(name) == name + assert name not in (os.curdir, os.pardir) + def test_splitext(self) -> None: assert ("wheel", ".whl") == Link("http://yo/wheel.whl").splitext() @@ -244,3 +305,53 @@ def test_links_equivalent(url1: str, url2: str) -> None: ) def test_links_equivalent_false(url1: str, url2: str) -> None: assert not links_equivalent(Link(url1), Link(url2)) + + +@pytest.mark.parametrize( + "name", + [ + "wheel.whl", + "myproject-1.0+foobar.0-py2.py3-none-any.whl", + # A literal "%2F" is a normal file name, not a separator. + "a%2Fb.whl", + ], +) +def test_as_path_component_keeps_plain_name(name: str) -> None: + assert as_path_component(name) == name + + +@pytest.mark.parametrize( + "name", + [ + os.path.join(os.sep, "abs", "pkg.whl"), + os.path.join("..", "pkg.whl"), + os.path.join("nested", "pkg.whl"), + ], +) +def test_as_path_component_reduces_to_basename(name: str) -> None: + # A name carrying directory components is reduced to its basename, so the + # result always stays inside the directory it is later joined onto. + assert as_path_component(name) == os.path.basename(name) + + +@pytest.mark.parametrize("name", ["", ".", "..", "/", os.path.join("sub", "..")]) +def test_as_path_component_rejects_empty_or_parent_reference(name: str) -> None: + with pytest.raises(ValueError): + as_path_component(name) + + +@pytest.mark.parametrize( + "name", + [ + "pkg.whl", + # A literal "%2F" is a normal file name, not a separator. + "a%2Fb.whl", + ], +) +def test_join_within_directory_stays_inside(name: str) -> None: + # The component is joined onto the directory as its final element, so the + # result stays inside the directory. + directory = os.path.join("base", "downloads") + joined = join_within_directory(directory, as_path_component(name)) + assert joined == os.path.join(directory, name) + assert os.path.basename(joined) == name From 4e9db3261c01983c95ed7abb0534ab65fe46abe3 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:21:23 -0500 Subject: [PATCH 04/12] Backport CVE-2026-6357: run the pip self-version check before installing GHSA-jp4c-xjxw-mgf9. The self-version check ran after the command body in a finally block, and its imports are deliberately deferred (cli/index_command.py lazily imports pip._internal.self_outdated_check and its dependencies). After `pip install` had just written new wheels into site-packages, those deferred imports could resolve to modules from the freshly installed packages, e.g. a top-level _manylinux.py, which packaging's tag logic imports while the check builds its PackageFinder. Split the check into a fetch phase that runs before the command body (all network and metadata work, and every import) and an emit phase that only logs the captured UpgradePrompt afterwards. Command.handle_pip_version_check() becomes the pip_version_check() context manager; InstallCommand skips the check when pip itself is a requirement. Upstream: pypa/pip#13923 (b369bfc96cc), released in 26.1. Cherry-picked; only the index_command.py import block conflicted (26.0.1 also imports sys there). Co-Authored-By: Claude Opus 5.5 --- 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 | 13 ++- src/pip/_internal/self_outdated_check.py | 69 ++++++------ tests/unit/test_base_command.py | 8 +- tests/unit/test_commands.py | 44 ++++++-- tests/unit/test_self_check_outdated.py | 132 +++++++++++++++++------ 9 files changed, 237 insertions(+), 97 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 0000000000..cb3b0ff6a0 --- /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 499a46f964..fa7694d7c7 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -2,12 +2,14 @@ from __future__ import annotations +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 Callable @@ -80,7 +82,8 @@ def __init__(self, name: str, summary: str, isolated: bool = False) -> None: 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. @@ -88,16 +91,15 @@ def handle_pip_version_check(self, options: Values) -> None: # 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 0f1ee1a4f8..ee07066497 100644 --- a/src/pip/_internal/cli/index_command.py +++ b/src/pip/_internal/cli/index_command.py @@ -8,9 +8,11 @@ from __future__ import annotations +import contextlib import logging import os import sys +from collections.abc import Iterator from functools import lru_cache from optparse import Values from typing import TYPE_CHECKING @@ -26,6 +28,7 @@ from pip._vendor.packaging.utils import NormalizedName from pip._internal.network.session import PipSession + from pip._internal.self_outdated_check import UpgradePrompt logger = logging.getLogger(__name__) @@ -139,10 +142,18 @@ def _build_session( 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): @@ -169,7 +180,8 @@ def should_exclude_prerelease( # No specific setting: exclude prereleases by default return True - 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. @@ -179,17 +191,27 @@ def handle_pip_version_check(self, options: Values) -> None: 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 8c3d86648b..b79e4f9599 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -1,14 +1,17 @@ from __future__ import annotations +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 pathlib import Path +from pip._vendor.packaging.requirements import InvalidRequirement, Requirement from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.requests.exceptions import InvalidProxyURL from pip._vendor.rich import print_json @@ -63,6 +66,14 @@ 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: @@ -278,6 +289,17 @@ def add_options(self) -> None: ), ) + @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 f9bad7e0d2..bc8a5696ba 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -1,8 +1,9 @@ from __future__ import annotations +import contextlib import json import logging -from collections.abc import Generator, Sequence +from collections.abc import Generator, Iterator, Sequence from email.parser import Parser from optparse import Values from typing import TYPE_CHECKING, cast @@ -135,9 +136,13 @@ def add_options(self) -> None: self.parser.insert_option_group(0, selection_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 e131ec8f84..4b5248ed3d 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -1,7 +1,6 @@ from __future__ import annotations import datetime -import functools import hashlib import json import logging @@ -9,7 +8,6 @@ import os.path import sys from dataclasses import dataclass -from typing import Callable from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import parse as parse_version @@ -156,16 +154,6 @@ def __rich__(self) -> Group: ) -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 ) -> str | None: @@ -194,28 +182,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[[], str | None], +def _compute_upgrade_prompt( + local_version: Version, remote_version_str: str, installed_by_pip: bool ) -> UpgradePrompt | None: - 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 = ( @@ -228,28 +203,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 + 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 d3e1a75272..23abf8b0b6 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -100,14 +100,14 @@ def test_raise_broken_stdout__debug_logging( 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_debug_enables_verbose_logs() -> None: diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 04ddd0316c..fef448276c 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -95,16 +95,16 @@ def has_option_no_index(command: Command) -> bool: @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, @@ -112,26 +112,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. @@ -144,7 +170,7 @@ def is_requirement_command(command: Command) -> bool: @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 4b3c36f682..4e6b68e1ad 100644 --- a/tests/unit/test_self_check_outdated.py +++ b/tests/unit/test_self_check_outdated.py @@ -7,7 +7,7 @@ import sys from optparse import Values from pathlib import Path -from unittest.mock import ANY, Mock, patch +from unittest.mock import Mock, patch import pytest from freezegun import freeze_time @@ -15,10 +15,22 @@ 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 +from pip._internal.self_outdated_check import ( + UpgradePrompt, + pip_self_version_check_emit, + pip_self_version_check_fetch, +) from pip._internal.utils.misc import ExternallyManagedEnvironment +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( "key, expected", [ @@ -37,32 +49,60 @@ 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") +@patch("pip._internal.self_outdated_check.get_default_environment") @patch("pip._internal.self_outdated_check.check_externally_managed", new=lambda: None) -def test_pip_self_version_check_calls_underlying_implementation( - mocked_state: Mock, mocked_function: Mock, tmpdir: Path +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( [ # noqa: PT006 - String representation is too long "installed_version", @@ -94,27 +134,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, "was_installed_by_pip", lambda _: installed_by_pip + self_outdated_check, + "_get_current_remote_pip_version", + lambda session, options: remote_version, ) - mock_state = Mock() - mock_state.get.return_value = stored_version - fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0) + mock_state_instance = Mock() + mock_state_instance.get.return_value = stored_version + monkeypatch.setattr( + self_outdated_check, + "SelfCheckState", + Mock(return_value=mock_state_instance), + ) + 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}", @@ -122,9 +176,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 ) @@ -196,13 +250,29 @@ def test_writes_expected_statefile(self, tmpdir: Path) -> None: assert statefile_permissions == selfcheckdir_permissions == cache_permissions -@patch("pip._internal.self_outdated_check._self_version_check_logic") -def test_suppressed_by_externally_managed(mocked_function: Mock, tmpdir: Path) -> None: - mocked_function.return_value = UpgradePrompt(old="1.0", new="2.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"), ): - pip_self_version_check(session=Mock(), options=fake_options) - mocked_function.assert_not_called() + 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() From 3e9477ab0ee7af73e0ecf354486c1e4a338e3a3b Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:21:31 -0500 Subject: [PATCH 05/12] Upgrade vendored certifi to 2026.10.19 GHSA-6q48-6c7w-9gw2 (critical, no CVE): certifi releases from 2019.03.09 up to 2026.10.19 bundle the emSign ECC Root CA - G3, whose intermediate CA keys were compromised. The vendored certifi 2026.1.4 is affected. Vendor certifi 2026.10.19, the release vendored by upstream pip 26.2.2. LICENSE, __init__.py, __main__.py, cacert.pem, core.py and py.typed are byte-identical to pip 26.2.2's vendored copy (pip 26.2.2 also carries a certifi/tests directory that 26.0.1 never vendored; it is not added). core.py and tools/vendoring/patches/certifi.patch are unchanged from 26.0.1, so the patch still applies. The bundle drops emSign ECC Root CA - G3 and the other roots Mozilla removed since 2026.1.4 (136 -> 120 certificates). Co-Authored-By: Claude Opus 5.5 --- src/pip/_vendor/certifi/__init__.py | 2 +- src/pip/_vendor/certifi/cacert.pem | 836 +++++----------------------- src/pip/_vendor/vendor.txt | 2 +- 3 files changed, 154 insertions(+), 686 deletions(-) diff --git a/src/pip/_vendor/certifi/__init__.py b/src/pip/_vendor/certifi/__init__.py index 090fd58487..5e43134634 100644 --- a/src/pip/_vendor/certifi/__init__.py +++ b/src/pip/_vendor/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2026.01.04" +__version__ = "2026.10.19" diff --git a/src/pip/_vendor/certifi/cacert.pem b/src/pip/_vendor/certifi/cacert.pem index 132db0df1b..156dc85188 100644 --- a/src/pip/_vendor/certifi/cacert.pem +++ b/src/pip/_vendor/certifi/cacert.pem @@ -1,316 +1,4 @@ -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln -biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF -MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT -d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 -76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ -bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c -6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE -emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd -MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt -MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y -MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y -FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi -aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM -gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB -qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 -lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn -8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov -L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 -45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO -UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 -O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC -bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv -GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a -77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC -hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 -92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp -Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w -ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt -Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - # Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited # Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited # Label: "COMODO ECC Certification Authority" @@ -335,105 +23,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - # Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" @@ -1097,44 +686,6 @@ aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - # Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center # Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center # Label: "T-TeleSec GlobalRoot Class 2" @@ -1166,35 +717,6 @@ g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN BSeOE6Fuwg== -----END CERTIFICATE----- -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - # Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited # Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited # Label: "QuoVadis Root CA 1 G3" @@ -2483,29 +2005,6 @@ RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- -# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign ECC Root CA - G3" -# Serial: 287880440101571086945156 -# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 -# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 -# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b ------BEGIN CERTIFICATE----- -MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG -EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo -bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g -RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ -TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s -b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 -WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS -fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB -zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq -hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB -CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD -+JbNR6iC8hZVdyR+EhCVBCyj ------END CERTIFICATE----- - # Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI # Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI # Label: "emSign Root CA - C1" @@ -2725,96 +2224,6 @@ PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE QRBdJ3NghVdJIgc= -----END CERTIFICATE----- -# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global Certification Authority" -# Serial: 1846098327275375458322922162 -# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e -# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 -# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 ------BEGIN CERTIFICATE----- -MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x -ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 -c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx -OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI -SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI -b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn -swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu -7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 -1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW -80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP -JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l -RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw -hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 -coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc -BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n -twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud -DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W -0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe -uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q -lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB -aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE -sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT -MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe -qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh -VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 -h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 -EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK -yeC2nOnOcXHebD8WpHk= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P256 Certification Authority" -# Serial: 4151900041497450638097112925 -# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 -# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf -# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 ------BEGIN CERTIFICATE----- -MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG -SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN -FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w -DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw -CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh -DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P384 Certification Authority" -# Serial: 2704997926503831671788816187 -# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 -# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 -# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 ------BEGIN CERTIFICATE----- -MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB -BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ -j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF -1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G -A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 -AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC -MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu -Sw== ------END CERTIFICATE----- - # Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. # Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. # Label: "NAVER Global Root Certification Authority" @@ -3375,45 +2784,6 @@ Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c -----END CERTIFICATE----- -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 159662449406622349769042896298 -# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc -# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 -# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt -nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY -6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu -MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k -RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg -f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV -+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo -dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW -Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa -G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq -gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H -vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 -0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC -B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u -NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg -yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev -HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 -xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR -TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg -JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV -7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl -6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL ------END CERTIFICATE----- - # Issuer: CN=GTS Root R3 O=Google Trust Services LLC # Subject: CN=GTS Root R3 O=Google Trust Services LLC # Label: "GTS Root R3" @@ -4066,30 +3436,6 @@ ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= -----END CERTIFICATE----- -# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" -# Serial: 65916896770016886708751106294915943533 -# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 -# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 -# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a ------BEGIN CERTIFICATE----- -MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf -e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C -cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O -BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw -hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG -XSaQpYXFuXqUPoeovQA= ------END CERTIFICATE----- - # Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA # Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA # Label: "TWCA CYBER Root CA" @@ -4130,35 +3476,6 @@ xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X -----END CERTIFICATE----- -# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Label: "SecureSign Root CA12" -# Serial: 587887345431707215246142177076162061960426065942 -# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 -# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 -# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u -LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw -NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD -eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS -b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF -KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt -p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd -J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur -FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J -hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K -h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF -AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld -mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ -mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA -8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV -55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ -yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== ------END CERTIFICATE----- - # Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. # Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. # Label: "SecureSign Root CA14" @@ -4429,7 +3746,7 @@ YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= # Issuer: CN=OISTE Server Root RSA G1 O=OISTE Foundation # Subject: CN=OISTE Server Root RSA G1 O=OISTE Foundation -# Label: " OISTE Server Root RSA G1" +# Label: "OISTE Server Root RSA G1" # Serial: 113845518112613905024960613408179309848 # MD5 Fingerprint: 23:a7:9e:d4:70:b8:b9:14:57:41:8a:7e:44:59:e2:68 # SHA1 Fingerprint: f7:00:34:25:94:88:68:31:e4:34:87:3f:70:fe:86:b3:86:9f:f0:6e @@ -4466,3 +3783,154 @@ J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy BiElxky8j3C7DOReIoMt0r7+hVu05L0= -----END CERTIFICATE----- + +# Issuer: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Subject: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Label: "e-Szigno TLS Root CA 2023" +# Serial: 71934828665710877219916191754 +# MD5 Fingerprint: 6a:e9:99:74:a5:da:5e:f1:d9:2e:f2:c8:d1:86:8b:71 +# SHA1 Fingerprint: 6f:9a:d5:d5:df:e8:2c:eb:be:37:07:ee:4f:4f:52:58:29:41:d1:fe +# SHA256 Fingerprint: b4:91:41:50:2d:00:66:3d:74:0f:2e:7e:c3:40:c5:28:00:96:26:66:12:1a:36:d0:9c:f7:dd:2b:90:38:4f:b4 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS RSA Root CA 2024" +# Serial: 17188327524208271538 +# MD5 Fingerprint: d0:a4:db:32:eb:44:98:d2:62:0b:3e:bc:4d:7c:5c:e9 +# SHA1 Fingerprint: fb:97:96:7c:ef:8d:98:63:06:c0:3b:b6:11:f8:e0:13:97:a2:98:d3 +# SHA256 Fingerprint: 14:35:f2:25:c5:d2:52:d7:a2:19:48:cc:3c:e6:2a:ec:fa:88:00:1e:3d:d7:2d:1c:c3:55:51:00:eb:37:2f:93 +-----BEGIN CERTIFICATE----- +MIIFmjCCA4KgAwIBAgIJAO6JNNDLgOCyMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNV +BAYTAkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEj +MCEGA1UEAxMaU0VDT00gVExTIFJTQSBSb290IENBIDIwMjQwHhcNMjQwMTMxMDUx +MTU1WhcNNDkwMTE0MDUxMTU1WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VD +T00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBS +U0EgUm9vdCBDQSAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4TjizUwzxbInq8Tx11gaFYNk5fO+34y7TyM4neh0UgL5JIZbJNLTz2x//L/B71+5 +m6X6nGIr7d4lFJBGtjO677hXOz93zkcWaUTm3VbOAjBlt4YWxlcccBHXuZ7o3Q+4 +R+ormrBdHeJ1CTUEG8ttQbKIl3G7OZYbnH8/pP8cjPub/0kDVNuMzp7xsVRROOis +Qt53fMoJLlYgoebbuMphOqMCtjkJ7R6efEMfLp8UAVi9ZaLRn76ET/CJkk925ndu +uufC4BatS4mnXFmxN0vUXb0ij9B8O/D8gixQEsVSD4GK8FWRPh3bVd/6bzdkHGJj +y21XI0yejVomZUbRrOfNuz0boPGV1pt18fFC39IHQEth3OFqb5NDO3L+A9bNqTgA +yUgRmIn4ucgDc/Ri/Km3V51ueZjy1/yk0qwJVadAVVrCt56iNeXOyEvzJADGgDQ8 +E1Pdaqct8Cynz/47ReQM62vFYO08wcQkrjmX/tesiko1V1yyaf6EfPzUFzmaGy9x +vkCwdbm15EdTolOjE0H2Vb5/APDOyCFEokiYGmXTLdAUl0wKZ4IyjkHGzy0jhpaX +EXE/GJcEvI6VzEchjaBL03EJ0h9pG4OqeIOycKvAo3A+TbetyfsrgYyHzU0a7/qU +jGat1AAq1nVljMpKqpinPTsf/d9H39FTUeJL7TpzzjUCAwEAAaNjMGEwHQYDVR0O +BBYEFCzrchKOWHdkNRVWNQFXB6l9DTbmMB8GA1UdIwQYMBaAFCzrchKOWHdkNRVW +NQFXB6l9DTbmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAVwsvluSafaez5tFPR/hRTBzRxEyMMQF3XJXCVi3yegZyK +oec7hmE6jx2ZM8KgM1kn2yJRwFXHX8zUW9nBLEDWc4wuE8LrlZqhGZM9pJQXGmGz +ResDJV6JgRBna+j4sA1M7yIdlvL0sAfFXFCTRaWTD4E1V99RLrFzWfTcC+e180hD +uNMpqOEo46+lMeW/Wvh7ifOQs+kiK0O2gHxQDNxslSavnCs4V7l8HRDJ2La10o70 +Bo7VLzf1W8MBvv0VTnxB+NjT5qTAbhGFh9Gvp4BaJpmdUf0C5CEP6dbQlfgxWfzY +r69yVT6dPQB+GFEaY03IMY+AcBCs+om1fNxrQXt9zoofMBNFbLhvpNH/JsXWdGUz +fNbO12uswTa5wah8LB18FTQN2/zPHYmvBEoLuyUgZ09VNLJo5YA0kXItVYkLjMe2 +SixzK4scUHv81IK99I91DWx7FwMVKw2xgFp+ZLYB2dnpQQrqwlW64glHUcK2N9BD +snjLSxeZ+UPECh9RxH4WAcKiZW+cqaKMmhP2WBfR4IcR7NOL32ml11ds87hhV1CZ +WWFCJAcCidYZz6CZa8exzHojP9SB5RH0/v1KdHAisqhSjtJl/UIAHIQ48elOn8wr +TdFap4Yb5aHglmMeNx+fAIhDluWVfxTO7H4dTPU+SFVRMLAh+wwKZfqb94nMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS ECC Root CA 2024" +# Serial: 9329818985461676612 +# MD5 Fingerprint: 99:d3:9d:e4:d2:b1:2d:f0:2a:04:67:85:f3:df:46:d6 +# SHA1 Fingerprint: 7a:1f:22:2d:72:b2:c3:19:87:44:db:61:69:e8:a6:4b:d7:0d:44:0e +# SHA256 Fingerprint: 6a:b2:ab:75:f5:1c:b4:f4:f0:15:62:03:fb:f6:f6:46:23:2f:51:4b:e0:59:f6:28:33:30:8b:82:b4:d7:2d:b1 +-----BEGIN CERTIFICATE----- +MIICTDCCAdGgAwIBAgIJAIF6LO+PI3pEMAoGCCqGSM49BAMDMFoxCzAJBgNVBAYT +AkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEG +A1UEAxMaU0VDT00gVExTIEVDQyBSb290IENBIDIwMjQwHhcNMjQwMTMxMDU1MjM0 +WhcNNDkwMTE0MDU1MjM0WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VDT00g +VHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBFQ0Mg +Um9vdCBDQSAyMDI0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7NzFMtu9dzQXSNC1 +2fabk0+GlC5finB3R7XaZonRUd20aFiWObtuNBCLUZSfk6QXAE55BjEXsXQ/NG8y +UqicXjsu9ksDK3JZBgCwLOVh6+nwJXTvso/dEj/GUYH5mBdoo2MwYTAdBgNVHQ4E +FgQUO3YReyl04k4GTFaCQNAhL3qzydUwHwYDVR0jBBgwFoAUO3YReyl04k4GTFaC +QNAhL3qzydUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI +zj0EAwMDaQAwZgIxAN3ib8fi1pMYtAPjMilB5e5/H+t5CL0xPL+cZ5oTTZuSCjpA +n1v7F/VAr8bFxQXAowIxAKsBVO1ACFp7skwzPvdv1EUY5a897WGLT4lb+bjxFAWy +l8wDcZJdwGZ/pAHxt1AJ1g== +-----END CERTIFICATE----- + +# Issuer: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Label: "Telia EC TLS Root CA v3" +# Serial: 8028200332079443110287896228123317 +# MD5 Fingerprint: b6:fa:6a:5c:42:fb:c5:67:72:c0:e0:2f:72:fb:5a:44 +# SHA1 Fingerprint: b4:d6:07:c2:a5:95:bc:5b:f4:67:4d:c9:dc:6f:6f:0a:00:7a:a5:35 +# SHA256 Fingerprint: 09:8e:08:a9:1d:bb:f7:74:78:b9:6c:ce:b8:9b:14:13:a5:da:37:b7:c8:62:60:6a:95:5d:eb:07:17:9f:43:26 +-----BEGIN CERTIFICATE----- +MIICMjCCAbegAwIBAgIPAYvSIlRjTQSLbOVHH9K1MAoGCCqGSM49BAMDMEoxCzAJ +BgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdU +ZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzAeFw0yMzExMTUwODU1MjZaFw00ODA1MjMx +MTAwMDBaMEoxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFC +MSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABMHIlhVDLbmFKUpW0iK4dpryT6emYOeS31JPwWnWPmkWRrAkTbPX +40sQfHI9mpR7Rbktu3ngg6W+BBSXSechtMCnBmWXj/EaVlmV5cY1jD2HoTfhBQ3A +acpCNMLJK4NpZaNjMGEwHwYDVR0jBBgwFoAU1GToQ4g6cy/QGnGCNgtehd7H3kMw +HQYDVR0OBBYEFNRk6EOIOnMv0BpxgjYLXoXex95DMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2kAMGYCMQCXAUdS/9bbJ8A1JYaG +f/bWt/s7Ta0ot5Ulno8OjSNYRWQIlS4tVWldvTAVA7heOFgCMQCvKr8+Z2Rn+OBr +5UHzlgBObpad1LuwNTRcdNgUJxIWadcki+UBLEi1/AURKV5md2M= +-----END CERTIFICATE----- + +# Issuer: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Label: "Telia RSA TLS Root CA v3" +# Serial: 8028214673410753541795188766939845 +# MD5 Fingerprint: f4:8b:9c:f3:f8:63:cf:dc:25:8f:b4:bb:a2:e9:9d:e2 +# SHA1 Fingerprint: b5:2e:88:4e:40:c1:11:fb:50:c7:e2:4f:ac:18:2b:bd:68:15:d2:34 +# SHA256 Fingerprint: d1:3d:b1:29:4c:45:eb:c6:fc:86:c6:bb:f6:9f:a2:9b:df:e6:92:df:f7:c7:13:c2:43:c7:a9:56:c6:a2:28:4c +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgIPAYvSUKtCVSxHWr2h3BrFMA0GCSqGSIb3DQEBDAUAMEsx +CzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQD +DBhUZWxpYSBSU0EgVExTIFJvb3QgQ0EgdjMwHhcNMjMxMTE1MDk0NzQyWhcNNDgw +NTIzMTEwMDAwWjBLMQswCQYDVQQGEwJTRTEZMBcGA1UECgwQVGVsaWEgQ29tcGFu +eSBBQjEhMB8GA1UEAwwYVGVsaWEgUlNBIFRMUyBSb290IENBIHYzMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsV89KG19hCf4S1Fvk8D3TyDERhmcvx8F +7Kmb4WATx3ije1id3KHxRE0TKmcNCbAQ57bvHFEYa4hR2l20VjVadExqOW+2ld99 +MbEiO+jRVOz+BbxLxJnmGwCqI+BfuTjjVReDxsxjQvjgBsClaO/sm5i70nlZcWGR +tIkvWDK3NNkT5RtwXc/O8NTFVpbUqT6cRjIj3olAblR+lRf4Ffy5o+Q9fabjYn9Z +9S4itruElcEFf9Ljk7fwdTycT/rvJW9w/B3G2a3r0f/zXNOVruIBcqE6pkSospAC +U2bG42fYKrbM/GWnp7u+p9Frz4jaNwpb4YHuEeS8BratNcP8X62jXIvvKHxlsMDJ +Cnb4U8JzFOLsU6mohVY58BdZrvi0Gk9UOuqmgoG6dskHoksjZTlK61D/InzmEoA1 +yAYJFDVysjRxDUOu9cAwANbqmq77WIFL6BpnZgVqPtMfG6wN8BrTKdapvilVsYR5 +9BFgIsAVBMxrGh+W+QcvmJafUpASvlArKvVG2FI4i6PiLjSBT0+6F6EQLrYqefOQ +F/fBNEXb+njUQ0SUVrAqtH4Y+OjCI/a4/JJQppxeemZcQ0SUShgiI5AM5xHO5iya +UrTjYH4zxUz9j+1FEbDH/xpstr1gXBykspup+hRTaJcbA+UbpJqtWZndAPddJmt6 +YJQ+dU3pDu8CAwEAAaNjMGEwHwYDVR0jBBgwFoAUsMep0t2yKFZzBJSMFFxIbzdS +kqgwHQYDVR0OBBYEFLDHqdLdsihWcwSUjBRcSG83UpKoMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBdYzFsNGDRk7bR +/AgRKq+5637YuOW+w6uhpoS0VnKMUpyHCwku86hEvqivakPtfmlm4bFwt++sb/8O +XsWBqtfbXMaBNDTZl8XRMJuLWOW2JrbKkRzgG0eBUcvsadG1rrhbmZqYvFXaAZO7 +o4TdOZzxhBB5GOAWWXB3IeraNP4J63zyo9n8Gqw3sJBG44em5hoYjBffP+npibys +lnslRi4L6xHsCYj/Pab+OlqbMCB6v+sTCLeEIukRVzoR9aQ45pEK7Z1QBnSsbAKQ +tss0JKD9d/mX143H1xePjPhTXlv5JCkhrcj+SShz0P9+EHoWe6m9lyUEOIVn0rp+ +yVJWNbmyDv3VkwFxHC1ApSQsgSimjGQ4wtr6cSmordYxkV+Ro8lOIIhRksXPyDk2 +7gW6IjUXCkZKpxFjkL3jiBSc8SkxnwCWtXg8xwNwdFVNBGLCCuJnsneYXjJNqzRq +UcoGwzsvF3Qi/ZnHUNvISdevlgIAXL4Wvrxaqvoa01wB+GCfs57RTGE4TvAGhKNK +us8K3hRT1BSpigzMIRzSxtAOrqPN6j//QSmW9f8Jcncri4j2ihSpVrFU0NdNkMhZ +eAKidTFPsxCVFuW4Aniz7jqiw5sWtjbQrlW035izIEU4sYwQoC1Nx0Svy+mMTRai +50LqFQ+A1/Hq6xHHDNx7CI83d23Erw== +-----END CERTIFICATE----- diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index dd2f5f3485..677e52a931 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -6,7 +6,7 @@ packaging==26.0 platformdirs==4.5.1 pyproject-hooks==1.2.0 requests==2.32.5 - certifi==2026.1.4 + certifi==2026.10.19 idna==3.11 urllib3==1.26.20 rich==14.2.0 From d6dd06eac847e16f8df301652f9377560ce1b5f2 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Wed, 21 Oct 2026 18:21:45 -0500 Subject: [PATCH 06/12] Release 26.0.1.post1: backport four pip CVEs and vendored certifi 2026.10.19 Bump version 26.0.1 -> 26.0.1.post1 (PEP 440 post-release) and add a NEWS section covering CVE-2026-3219, CVE-2026-6357, CVE-2026-8643, CVE-2026-13346 and the vendored certifi upgrade (GHSA-6q48-6c7w-9gw2, emSign). CVE-2026-1703 and CVE-2025-8869 are noted as already fixed in 26.0.1. The upstream news/ fragments brought in by the cherry-picks are removed: their content is folded into the NEWS section above, and leaving them would make a towncrier run list the same changes again. Co-Authored-By: Claude Opus 5.5 --- NEWS.rst | 39 +++++++++++++++++++++++++++++++++++++++ news/13867.bugfix.rst | 1 - news/13923.trivial.rst | 2 -- news/14000.bugfix.rst | 2 -- news/14110.bugfix.rst | 1 - src/pip/__init__.py | 2 +- 6 files changed, 40 insertions(+), 7 deletions(-) delete mode 100644 news/13867.bugfix.rst delete mode 100644 news/13923.trivial.rst delete mode 100644 news/14000.bugfix.rst delete mode 100644 news/14110.bugfix.rst diff --git a/NEWS.rst b/NEWS.rst index cad4e825f3..fde6524678 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,6 +9,45 @@ .. towncrier release notes start +26.0.1.post1 (2026-10-21) +========================= + +Security Fixes +-------------- + +- **CVE-2026-3219**: Disambiguate archive type detection in ``unpack_file`` + so a tar+zip polyglot is no longer always treated as a zip; ambiguous + archives are rejected. (`GHSA-58qw-9mgm-455v `_) +- **CVE-2026-6357**: Run the network and import side of the pip + self-version check before the command body, so a just-installed wheel + cannot shadow a module the check imports afterwards; only the upgrade + prompt is printed after the command. (`GHSA-jp4c-xjxw-mgf9 `_) +- **CVE-2026-8643**: Reject ``console_scripts`` and ``gui_scripts`` entry + points whose name would install a script outside the scripts directory. + The vendored distlib 0.4.0 accepts such names unchanged, so this release + was directly affected. (`GHSA-wf93-45jw-7689 `_) +- **CVE-2026-13346**: Stop ``Link.filename`` percent-decoding the URL path a + second time, so a doubly-encoded separator from a malicious index (e.g. + ``..%252F..%252Fevil.whl``) can no longer write a download outside the + download directory. Download file names are now always validated as a + single path component. (`GHSA-qwm4-qh6w-59xr `_) +- **GHSA-6q48-6c7w-9gw2**: Upgrade vendored certifi from 2026.1.4 to + 2026.10.19 (the release vendored by pip 26.2.2), dropping the emSign ECC + Root CA - G3, whose intermediate CA keys were compromised. (`GHSA-6q48-6c7w-9gw2 `_) + +.. note + + CVE-2026-1703 / GHSA-6vgw-5pg2-w6jp (``is_within_directory`` prefix + comparison) and CVE-2025-8869 / GHSA-4xh5-x5gv-qwph (tar symlink targets + on the extraction fallback) were already fixed upstream before pip + 26.0.1. + + This is an ActiveState security release for Python 3.9, the last Python + version pip 26.0.x supports (pip 26.1 requires Python 3.10). It uses the + PEP 440 post-release scheme: ``26.0.1.post1`` sorts after ``26.0.1`` and + before ``26.0.2``. + + 26.0.1 (2026-02-04) =================== diff --git a/news/13867.bugfix.rst b/news/13867.bugfix.rst deleted file mode 100644 index 4c68ae9f88..0000000000 --- a/news/13867.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix installing ``.tar.gz`` source distributions that look like a zip file. diff --git a/news/13923.trivial.rst b/news/13923.trivial.rst deleted file mode 100644 index cb3b0ff6a0..0000000000 --- a/news/13923.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -Split the pip self-version check into a fetch phase before the command -body and an emit phase afterwards. diff --git a/news/14000.bugfix.rst b/news/14000.bugfix.rst deleted file mode 100644 index 3b86f1b3b0..0000000000 --- a/news/14000.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Reject ``console_scripts`` and ``gui_scripts`` entry points whose name would -install a script outside the scripts directory. diff --git a/news/14110.bugfix.rst b/news/14110.bugfix.rst deleted file mode 100644 index f7d4f78882..0000000000 --- a/news/14110.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``Link.filename`` decoding the URL path twice. diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 978b412920..0a50fed5a6 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from __future__ import annotations -__version__ = "26.0.1" +__version__ = "26.0.1.post1" def main(args: list[str] | None = None) -> int: From dc648654d14670a0051152033f83645304f3a939 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:03:50 -0500 Subject: [PATCH 07/12] Review: harden the CVE-2025-8869 fix (port pypa/pip#14127) Reviewer finding (ActiveState/pip#7, blocker): 26.0.1 has upstream's 25.3 fix for CVE-2025-8869 (GHSA-4xh5-x5gv-qwph) but not the later hardening from pypa/pip#14127 (17eb176dc, released in 26.2). On the tar fallback path used when tarfile.data_filter is missing (Python 3.9.0-3.9.16), symlinks to "." or ".." are archive members, so is_symlink_target_in_tar accepts them, and a later member written through them lands outside the extract directory: - outside_link -> .., outside_link/outside.txt, then a ".." dir member: the file is written before the InstallationError is raised; - ., redir -> ., redir/up -> .., redir/up/outside.txt: no error at all. Port 17eb176dc: is_within_directory gains a keyword-only resolve_symlinks flag (realpath instead of abspath), each member is also checked after resolving symlinks, and a symlink whose target resolves outside the destination is rejected before it is created. Upstream's two regression tests are included. The only conflict was the is_within_directory docstring; the commonpath body is replaced separately in the next review commit. Co-Authored-By: Claude Opus 5.5 --- src/pip/_internal/utils/unpacking.py | 35 ++++++++++++-- tests/unit/test_utils_unpacking.py | 68 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index 879b40c37e..b21718a8ad 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -76,12 +76,22 @@ def has_leading_dir(paths: Iterable[str]) -> bool: return True -def is_within_directory(directory: str, target: str) -> bool: +def is_within_directory( + directory: str, target: str, *, resolve_symlinks: bool = False +) -> bool: """ Return true if the absolute path of target is within the directory + (including when target is equal to the directory). + + When ``resolve_symlinks`` is true, resolve symlinks before comparing so + traversal through a symlink (e.g. "link/../file") is also caught. """ - abs_directory = os.path.abspath(directory) - abs_target = os.path.abspath(target) + if resolve_symlinks: + abs_directory = os.path.realpath(directory) + abs_target = os.path.realpath(target) + else: + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) prefix = os.path.commonpath([abs_directory, abs_target]) return prefix == abs_directory @@ -277,7 +287,13 @@ def _untar_without_filter( if leading: fn = split_leading_dir(fn)[1] path = os.path.join(location, fn) - if not is_within_directory(location, path): + + # The plain check rejects textual ".." escapes; resolving symlinks also + # catches a later member redirected outside by an earlier member's + # symlink (e.g. "link/../file"). + if not is_within_directory(location, path) or not is_within_directory( + location, path, resolve_symlinks=True + ): message = ( "The tar file ({}) has a file ({}) trying to install " "outside target directory ({})" @@ -286,6 +302,17 @@ def _untar_without_filter( if member.isdir(): ensure_dir(path) elif member.issym(): + # Reject symlinks resolving outside the destination, so a later + # member cannot be written through them. + target = os.path.join(os.path.dirname(path), member.linkname) + if not is_within_directory(location, target, resolve_symlinks=True): + message = ( + "The tar file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError( + message.format(filename, member.name, member.linkname) + ) if not is_symlink_target_in_tar(tar, member): message = ( "The tar file ({}) has a file ({}) trying to install " diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 113c6df8c4..1b4fbcda8c 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -389,6 +389,74 @@ def test_unpack_evil_tar_link2_no_data_filter( assert not os.path.exists(os.path.join(extract_path, "evil_symlink")) + def test_unpack_tar_symlink_then_member_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """Reject a symlink to outside before a member is written through it.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + + tar_filepath = os.path.join(self.tempdir, "symlink_then_member.tar") + extract_path = os.path.join(self.tempdir, "extract_path") + outside_path = os.path.join(self.tempdir, "outside.txt") + + with tarfile.open(tar_filepath, "w") as tar: + info = tarfile.TarInfo("outside_link") + info.type = tarfile.SYMTYPE + info.linkname = ".." + tar.addfile(info) + + data = io.BytesIO(b"data\n") + info = tarfile.TarInfo("outside_link/outside.txt") + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + + info = tarfile.TarInfo("..") + info.type = tarfile.DIRTYPE + tar.addfile(info) + + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + + assert not os.path.exists(outside_path) + assert not os.path.exists(os.path.join(extract_path, "outside_link")) + + def test_unpack_tar_nested_symlink_traversal_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """Reject a member that escapes through a chain of in-bounds symlinks.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + + tar_filepath = os.path.join(self.tempdir, "nested_traversal.tar") + extract_path = os.path.join(self.tempdir, "extract_path") + outside_path = os.path.join(self.tempdir, "outside.txt") + + with tarfile.open(tar_filepath, "w") as tar: + info = tarfile.TarInfo(".") + info.type = tarfile.DIRTYPE + tar.addfile(info) + + info = tarfile.TarInfo("redir") + info.type = tarfile.SYMTYPE + info.linkname = "." + tar.addfile(info) + + info = tarfile.TarInfo("redir/up") + info.type = tarfile.SYMTYPE + info.linkname = ".." + tar.addfile(info) + + data = io.BytesIO(b"data\n") + info = tarfile.TarInfo("redir/up/outside.txt") + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + + assert not os.path.exists(outside_path) + def test_unpack_tar_unicode(tmpdir: Path) -> None: test_tar = tmpdir / "test.tar" From 4949a2118a5cfbeccaae26ad61adf98c3ce76098 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:05:10 -0500 Subject: [PATCH 08/12] Review: root-safe path containment for is_within_directory and _script_within_dir Reviewer findings (ActiveState/pip#7, should fix; same on #4/#5/#6): - is_within_directory used os.path.commonpath, which drops the POSIX "//" prefix, so `pip install --prefix //tmp/x ` failed with "trying to install outside the target directory" (already broken on the 26.0.1 base). Upstream fixed it in 7ff8bdd81 (#14001, 26.1.2) with startswith(abs_dir + os.sep), but that form rejects every target when the directory is a filesystem root ("/" or "C:\\"). - _script_within_dir used dest.startswith(root + os.sep), so a scripts directory of "/" or "C:\\" rejected every console/gui script. Both now use one prefix rule: a target is within a directory when it equals it or starts with the directory plus a separator, where a directory that already ends with a separator (a filesystem root) is used as-is. No commonpath, so a target on another Windows drive is simply outside instead of raising ValueError. _script_within_dir delegates to is_within_directory and still refuses a name that resolves to the scripts directory itself. Tests cover "//" roots, "/" as the directory and as the scripts directory, and the other-drive case on Windows; the existing CVE-2026-1703 prefix-sibling and CVE-2026-8643 cases still pass. Co-Authored-By: Claude Opus 5.5 --- src/pip/_internal/operations/install/wheel.py | 3 ++- src/pip/_internal/utils/unpacking.py | 9 +++++-- tests/unit/test_utils_unpacking.py | 25 +++++++++++++++++++ tests/unit/test_wheel.py | 9 +++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 231e400658..9676166764 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -405,7 +405,8 @@ def _script_within_dir(name: str, scripts_dir: str) -> bool: """ root = os.path.normpath(scripts_dir) dest = os.path.normpath(os.path.join(scripts_dir, name)) - return dest.startswith(root + os.sep) + # The script must be a file *inside* the directory, never the directory itself. + return dest != root and is_within_directory(root, dest) def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None: diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index b21718a8ad..13945c4d51 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -93,8 +93,13 @@ def is_within_directory( abs_directory = os.path.abspath(directory) abs_target = os.path.abspath(target) - prefix = os.path.commonpath([abs_directory, abs_target]) - return prefix == abs_directory + if abs_target == abs_directory: + return True + # A filesystem root ("/" or "C:\\") already ends with a separator; appending + # another would reject everything. abspath keeps a POSIX "//" prefix, which + # os.path.commonpath would drop. + prefix = abs_directory if abs_directory.endswith(os.sep) else abs_directory + os.sep + return abs_target.startswith(prefix) def _get_default_mode_plus_executable() -> int: diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 1b4fbcda8c..498d2e0f07 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -497,6 +497,31 @@ def test_is_within_directory(args: tuple[str, str], expected: bool) -> None: assert result == expected +@pytest.mark.skipif(sys.platform == "win32", reason="POSIX path semantics") +@pytest.mark.parametrize( + "directory, target, expected", + [ + # os.path.commonpath drops the POSIX "//" prefix; abspath keeps it. + ("//srv/env/bin", "//srv/env/bin/pip", True), + ("//srv/env/bin", "//srv/env/binfoo/pip", False), + # A filesystem root already ends with a separator. + ("/", "/usr/bin/pip", True), + ("/", "/", True), + ], +) +def test_is_within_directory_posix_roots( + directory: str, target: str, expected: bool +) -> None: + assert is_within_directory(directory, target) is expected + + +@pytest.mark.skipif(sys.platform != "win32", reason="drive letters only on Windows") +def test_is_within_directory_other_drive() -> None: + # A target on another drive is outside, and the check must not raise. + assert is_within_directory("C:\\env", "D:\\outside") is False + assert is_within_directory("C:\\", "C:\\env\\pip") is True + + @pytest.mark.parametrize( "is_zip, is_tar, unzip, untar, exception", [ diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index c1aafe5325..2f9bba70ab 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -724,6 +724,15 @@ def test_script_within_dir_allows_doubled_slash_root() -> None: assert wheel._script_within_dir("../outside", "//srv/env/bin") is False +@pytest.mark.skipif(WINDOWS, reason="POSIX filesystem root") +def test_script_within_dir_filesystem_root() -> None: + # A scripts directory at the filesystem root accepts ordinary scripts... + assert wheel._script_within_dir("pip", "/") is True + # ...but never the directory itself. + assert wheel._script_within_dir(".", "/") is False + assert wheel._script_within_dir("", "/") 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, From 6fd12e25b6643802a9382a331d753d1b0ffe6527 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:06:21 -0500 Subject: [PATCH 09/12] Review: accept normalized member names in is_symlink_target_in_tar Reviewer finding (ActiveState/pip#4, should fix; the same code is on every AS pip line): on the tar fallback path (no tarfile.data_filter), the symlink check looked the link target up with tar.getmember(), which needs the exact stored member name. Two valid archive shapes were therefore refused: - "./"-prefixed archives, as produced by `tar -C dir .`, e.g. ./pkg/link -> target.txt (stored as "./pkg/target.txt"); - a symlink to a directory that has no directory entry of its own and only exists as the parent of other members. Upstream 26.2.2 behaves the same; this is a deliberate improvement. The link target is now compared against the set of normalized member paths plus every implied parent directory. That set is computed once per archive and passed in (is_symlink_target_in_tar gains a keyword-only member_paths argument), so extraction stays linear. Containment is still enforced by the realpath checks from the previous review commit (pypa/pip#14127): a link to "." or ".." or to an absolute path is rejected there, and a link to a path that is not in the archive is still refused here. The parent walk stops at any fixed point: posixpath.dirname("//") is "//", so a "//"-prefixed member name would otherwise loop forever. Tests: both valid shapes unpack, a link to a non-member is refused, and collecting member paths terminates for a "//" member name. Co-Authored-By: Claude Opus 5.5 --- src/pip/_internal/utils/unpacking.py | 47 ++++++++++---- tests/unit/test_utils_unpacking.py | 92 ++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index 13945c4d51..8c2ac328b0 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -4,6 +4,7 @@ import logging import os +import posixpath import shutil import stat import sys @@ -263,18 +264,39 @@ def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo: tar.close() -def is_symlink_target_in_tar(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> bool: - """Check if the file pointed to by the symbolic link is in the tar archive""" - linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname) - - linkname = os.path.normpath(linkname) - linkname = linkname.replace("\\", "/") +def _tar_member_paths(tar: tarfile.TarFile) -> set[str]: + """Normalized member paths plus every implied parent directory.""" + paths = set() + for member in tar.getmembers(): + name = posixpath.normpath(member.name.replace("\\", "/")) + while name not in ("", ".", "/"): + paths.add(name) + parent = posixpath.dirname(name) + if parent == name: + # posixpath.dirname("//") == "//": stop at any fixed point. + break + name = parent + return paths + + +def is_symlink_target_in_tar( + tar: tarfile.TarFile, + tarinfo: tarfile.TarInfo, + *, + member_paths: set[str] | None = None, +) -> bool: + """Check if the file pointed to by the symbolic link is in the tar archive - try: - tar.getmember(linkname) - return True - except KeyError: - return False + Member names are compared after normalization, so "./"-prefixed archives + and links to a directory that only exists implicitly (as the parent of + other members) are accepted. Containment itself is enforced separately by + the realpath checks in _untar_without_filter. + """ + linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname) + linkname = posixpath.normpath(linkname.replace("\\", "/")) + if member_paths is None: + member_paths = _tar_member_paths(tar) + return linkname in member_paths def _untar_without_filter( @@ -287,6 +309,7 @@ def _untar_without_filter( # NOTE: This function can be removed once pip requires CPython ≥ 3.12.​ # PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure. # This feature is fully supported from CPython 3.12 onward. + member_paths = _tar_member_paths(tar) for member in tar.getmembers(): fn = member.name if leading: @@ -318,7 +341,7 @@ def _untar_without_filter( raise InstallationError( message.format(filename, member.name, member.linkname) ) - if not is_symlink_target_in_tar(tar, member): + if not is_symlink_target_in_tar(tar, member, member_paths=member_paths): message = ( "The tar file ({}) has a file ({}) trying to install " "outside target directory ({})" diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 498d2e0f07..69e1bfe962 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -17,6 +17,7 @@ from pip._internal.exceptions import InstallationError from pip._internal.utils.unpacking import ( + _tar_member_paths, is_within_directory, unpack_file, untar_file, @@ -457,6 +458,97 @@ def test_unpack_tar_nested_symlink_traversal_no_data_filter( assert not os.path.exists(outside_path) + def _make_tar(self, name: str, entries: list[tuple[str, str, str]]) -> str: + tar_filepath = os.path.join(self.tempdir, name) + with tarfile.open(tar_filepath, "w") as tar: + for member_name, kind, arg in entries: + info = tarfile.TarInfo(member_name) + if kind == "dir": + info.type = tarfile.DIRTYPE + tar.addfile(info) + elif kind == "sym": + info.type = tarfile.SYMTYPE + info.linkname = arg + tar.addfile(info) + else: + data = io.BytesIO(arg.encode()) + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + return tar_filepath + + def test_unpack_tar_dot_prefixed_symlink_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A "./"-prefixed archive (tar -C dir .) with an internal symlink.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "dot_prefixed.tar", + [ + ("./", "dir", ""), + ("./pkg", "dir", ""), + ("./pkg/target.txt", "file", "data\n"), + ("./pkg/link", "sym", "target.txt"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + untar_file(tar_filepath, extract_path) + link = os.path.join(extract_path, "pkg", "link") + assert os.path.islink(link) + assert os.readlink(link) == "target.txt" + + def test_unpack_tar_symlink_to_implicit_dir_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A symlink to a directory that has no directory entry of its own.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "implicit_dir.tar", + [ + # A second top-level member stops untar_file stripping "pkg/". + ("README", "file", "data\n"), + ("pkg/sub/f.txt", "file", "data\n"), + ("pkg/link", "sym", "sub"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + untar_file(tar_filepath, extract_path) + assert os.path.islink(os.path.join(extract_path, "pkg", "link")) + + def test_unpack_tar_symlink_to_non_member_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A symlink to a path that is not in the archive is still refused.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "non_member.tar", + [ + ("pkg/f.txt", "file", "data\n"), + ("pkg/link", "sym", "missing.txt"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + assert not os.path.lexists(os.path.join(extract_path, "pkg", "link")) + + +def test_tar_member_paths_terminates_on_doubled_slash() -> None: + """posixpath.dirname("//") == "//"; collecting parents must not loop.""" + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + data = io.BytesIO(b"x") + info = tarfile.TarInfo("//abs/f.txt") + info.size = 1 + tar.addfile(info, fileobj=data) + buf.seek(0) + with tarfile.open(fileobj=buf) as tar: + paths = _tar_member_paths(tar) + assert "//abs/f.txt" in paths + assert "//abs" in paths + def test_unpack_tar_unicode(tmpdir: Path) -> None: test_tar = tmpdir / "test.tar" From 2d186fb274adf957b1b5bb67956d81fd72274ad7 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:07:21 -0500 Subject: [PATCH 10/12] Review: report an unsafe download file name as a pip error, not a traceback Reviewer nit (ActiveState/pip#7; the same on #3-#6): when a download's file name is not a single safe path component, e.g. `Content-Disposition: filename=".."` with a content type that adds no extension, as_path_component raised a bare ValueError and pip printed a traceback (exit 2). The write was already blocked; only the reporting was poor. Upstream behaves the same. Add UnsafeFilenameError(InstallationError, ValueError) to pip's exceptions and raise it instead. It is still a ValueError, so existing callers and tests keep working, and pip's command loop reports InstallationError as a plain line. With a local server returning filename="..": before: Traceback ... ValueError: Unexpected file name derived from URL: '..' after: ERROR: Unexpected file name derived from URL: '..' (exit 1) Co-Authored-By: Claude Opus 5.5 --- src/pip/_internal/exceptions.py | 4 ++++ src/pip/_internal/models/link.py | 4 ++-- tests/unit/test_link.py | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 9ddda0e626..31fe45929f 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -192,6 +192,10 @@ class InstallationError(PipError): """General exception during installation""" +class UnsafeFilenameError(InstallationError, ValueError): + """A file name derived from a URL or header is not a single safe path component.""" + + class FailedToPrepareCandidate(InstallationError): """Raised when we fail to prepare a candidate (i.e. fetch and generate metadata). diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 1a64398733..d64f747c9d 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -17,7 +17,7 @@ NewType, ) -from pip._internal.exceptions import InvalidEggFragment +from pip._internal.exceptions import InvalidEggFragment, UnsafeFilenameError from pip._internal.utils.datetime import parse_iso_datetime from pip._internal.utils.filetypes import WHEEL_EXTENSION from pip._internal.utils.hashes import Hashes @@ -59,7 +59,7 @@ def as_path_component(name: str) -> PathComponent: """ component = _to_path_component(name) if not component: - raise ValueError(f"Unexpected file name derived from URL: {name!r}") + raise UnsafeFilenameError(f"Unexpected file name derived from URL: {name!r}") return component diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index bc8cb8ab9b..e494e95d84 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -5,7 +5,12 @@ import pytest -from pip._internal.exceptions import InvalidEggFragment, PipError +from pip._internal.exceptions import ( + InstallationError, + InvalidEggFragment, + PipError, + UnsafeFilenameError, +) from pip._internal.models.link import ( Link, as_path_component, @@ -340,6 +345,15 @@ def test_as_path_component_rejects_empty_or_parent_reference(name: str) -> None: as_path_component(name) +def test_as_path_component_error_is_installation_error() -> None: + # Still a ValueError for existing callers, but pip's command loop reports + # an InstallationError as a plain "ERROR:" line rather than a traceback. + with pytest.raises(UnsafeFilenameError) as exc_info: + as_path_component("..") + assert isinstance(exc_info.value, InstallationError) + assert isinstance(exc_info.value, ValueError) + + @pytest.mark.parametrize( "name", [ From 49ce20b1abb9895cfe1a8ce162e04c4d7d1d0f81 Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:08:30 -0500 Subject: [PATCH 11/12] Review: let _arg_refers_to_pip tolerate a bare ValueError Reviewer finding (ActiveState/pip#3, should fix; applied to every AS pip line for consistency): the CVE-2026-6357 backport calls _arg_refers_to_pip() before the command body to decide whether `pip install pip` should skip the version check. It only caught InvalidRequirement, but packaging can surface a bare ValueError (urlsplit's "Invalid IPv6 URL" for `foo @ http://[bad/x`), which would crash the pre-command check instead of letting pip's normal requirement parsing report the error. On this line the version check already runs inside _inner_run (pip's error handling), and 26.0.1's vendored packaging parses that input without raising, so this is defensive: catch (InvalidRequirement, ValueError) and return False. Note that `pip install --no-index "foo @ http://[bad/x"` still ends in a ValueError traceback raised later from Link.__init__; that is unchanged upstream 26.0.1 behaviour and outside this backport. Tests: the ValueError case (fails before this change) plus basic name matching. Co-Authored-By: Claude Opus 5.5 --- src/pip/_internal/commands/install.py | 4 +++- tests/unit/test_commands.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index b79e4f9599..2abf72ab2e 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -69,7 +69,9 @@ def _arg_refers_to_pip(arg: str) -> bool: try: req = Requirement(arg) - except InvalidRequirement: + except (InvalidRequirement, ValueError): + # Older packaging can surface a bare ValueError (e.g. from urlsplit on + # "foo @ http://[bad"); let pip's normal requirement parsing report it. return False return canonicalize_name(req.name) == "pip" diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index fef448276c..471027bd33 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -184,3 +184,25 @@ def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> Non version_check_mock.assert_called_once() else: version_check_mock.assert_not_called() + + +@pytest.mark.parametrize( + "exc", + [ValueError("Invalid IPv6 URL")], +) +def test_arg_refers_to_pip_tolerates_value_error(exc: Exception) -> None: + """A malformed requirement must not crash the pre-command version check.""" + from pip._internal.commands import install + + with mock.patch.object(install, "Requirement", side_effect=exc): + assert install._arg_refers_to_pip("foo @ http://[bad/x") is False + + +@pytest.mark.parametrize( + "arg, expected", + [("pip", True), ("pip==26.0.1", True), ("Pip", True), ("six", False)], +) +def test_arg_refers_to_pip(arg: str, expected: bool) -> None: + from pip._internal.commands import install + + assert install._arg_refers_to_pip(arg) is expected From 8bc4ab80170b8f1876776a0c98aa8f1c142e96ea Mon Sep 17 00:00:00 2001 From: icanhasmath Date: Thu, 22 Oct 2026 10:08:59 -0500 Subject: [PATCH 12/12] =?UTF-8?q?Review:=20NEWS=20=E2=80=94=20render=20not?= =?UTF-8?q?es,=20record=20review=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer nits (ActiveState/pip#7): the 26.0.1.post1 section's `.. note` lacked `::`, so reStructuredText treated it as a comment and it never rendered; and it claimed CVE-2025-8869 was "already fixed" in 26.0.1 although 26.0.1 lacked the pypa/pip#14127 hardening. - Make the note a real `.. note::` admonition (upstream's header comment on line 1 is intentionally left as a comment). Checked with docutils: the note now renders. - CVE-2025-8869: say it was fixed in 26.0.1 and is hardened here, and add a Security Fixes entry for the #14127 port. - Add a Bug Fixes subsection for the other review fixes: root-safe is_within_directory / _script_within_dir, normalized symlink member names, the clean error for unsafe download file names, and the _arg_refers_to_pip ValueError tolerance. Co-Authored-By: Claude Opus 5.5 --- NEWS.rst | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index fde6524678..a660de21ed 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -34,13 +34,38 @@ Security Fixes - **GHSA-6q48-6c7w-9gw2**: Upgrade vendored certifi from 2026.1.4 to 2026.10.19 (the release vendored by pip 26.2.2), dropping the emSign ECC Root CA - G3, whose intermediate CA keys were compromised. (`GHSA-6q48-6c7w-9gw2 `_) +- **CVE-2025-8869**: Harden the tar extraction fallback (used when + ``tarfile.data_filter`` is unavailable, i.e. Python 3.9.0-3.9.16), porting + pypa/pip#14127: members are also checked after resolving symlinks, and a + symlink that resolves outside the destination is rejected before it is + created. Previously a symlink to ``.`` or ``..`` let a later member be + written outside the extract directory. (`GHSA-4xh5-x5gv-qwph `_) -.. note +Bug Fixes +--------- + +- ``is_within_directory`` no longer uses ``os.path.commonpath``, so installs + under a POSIX ``//``-prefixed path (e.g. ``--prefix //tmp/x``) work again, + and a directory at a filesystem root (``/`` or ``C:\``) is handled; a + target on another Windows drive is reported as outside instead of raising. +- A scripts directory at a filesystem root no longer rejects every console + or GUI script (the CVE-2026-8643 check now uses ``is_within_directory``). +- The tar fallback's symlink check compares normalized member names, so + ``./``-prefixed archives and links to a directory that has no entry of its + own unpack again; containment is still enforced by the symlink-resolving + checks above. +- An unsafe download file name (e.g. ``Content-Disposition: filename=".."``) + is reported as a pip ``ERROR:`` instead of a ``ValueError`` traceback. +- The ``pip install pip`` detection used by the self-version check no longer + fails on a requirement that makes packaging raise a bare ``ValueError``. + +.. note:: CVE-2026-1703 / GHSA-6vgw-5pg2-w6jp (``is_within_directory`` prefix - comparison) and CVE-2025-8869 / GHSA-4xh5-x5gv-qwph (tar symlink targets - on the extraction fallback) were already fixed upstream before pip - 26.0.1. + comparison) was already fixed upstream before pip 26.0.1. + CVE-2025-8869 / GHSA-4xh5-x5gv-qwph (tar symlink targets on the + extraction fallback) was fixed in 26.0.1 and is hardened in this release + (see above). This is an ActiveState security release for Python 3.9, the last Python version pip 26.0.x supports (pip 26.1 requires Python 3.10). It uses the