# Avoiding compromised certificates in pip

**URL:** <https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602>\
**Category:** Packaging\
**Created:** [October 21, 2026, 6:39pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602 "2026-10-21T18:39:43Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![ichard26](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ichard26/32/5810_2.png) [@ichard26](https://discuss.python.org/u/ichard26)\
**Post date:** [October 21, 2026, 6:39pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/1 "2026-10-21T18:39:43Z")

</div>

Hi, another pip maintainer here 👋

I’ve been investigating alternative remedies for those unable to upgrade pip immediately. As far as I can tell, setting `PIP_CERT` to a valid CA bundle that has been patched (edit: AND setting `PIP_USE_DEPRECATED=legacy-certs` if on Python 3.10+, see below) is sufficient to stop pip from using the vendored `certifi` bundle.

Some caveats:

- The environment variable need to be used so the custom CA bundle is passed down to pip subprocesses used for installing build dependencies. Using the `--cert` flag is **not enough**.
- On Python 3.10 and higher, setting `PIP_USE_DEPRECATED=legacy-certs` (once again, the CLI option is insufficient) is **recommended** if your environment does not need the system trust store for pip to function.
  - [pip’s truststore logic always loads the `certifi` CA bundle as a fallback while setting up the SSL context.](https://github.com/pypa/pip/blob/9dbd9c36a75d332c84ce6e55004471ab0a474595/src/pip/_internal/cli/index_command.py#L49-L51) Experiments suggest that setting `PIP_CERT` also prevents the SSL context from being used for verification, but I haven’t been able to trace through the requests/Python SSL code to be sure of this fact.
  - The `legacy-certs` mode forces pip to rely solely on requests’ TLS handling and passing `--cert` or `PIP_CERT` will set the `verify` option on pip’s internal requests session object, which will prevent the `certifi` default bundle from being used. [https://github.com/pypa/pip/blob/2f4d4a836ed00076001376fbb0ce6dc4f22cdae2/src/pip/\_vendor/requests/adapters.py#L292-L306](https://github.com/pypa/pip/blob/2f4d4a836ed00076001376fbb0ce6dc4f22cdae2/src/pip/%5C_vendor/requests/adapters.py#L292-L306)

This remedy can be confirmed by passing a patched, empty, or near empty CA bundle via `PIP_CERT` and observing that the TLS verification fails despite the vendored `certifi` for a command like `PIP_CERT=bundle... pip install -r https://dv-g3b-emudhra.we-have-a-problem.com/`. (If it is vulnerable, then you should get a parse error that HTML is not a valid requirement file.)

I would investigate further to determine whether `PIP_USE_DEPRECATED=legacy-certs` is needed for Python 3.10 and higher, but I am very busy in my personal life so I won’t have the time to do so until _much later_, so I’m sharing what I have so far for now.

I have also not investigated `REQUESTS_CA_BUNDLE` at all.

---

<div class="post-metadata">

**Author:** ![tiran](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tiran/32/48_2.png) [@tiran](https://discuss.python.org/u/tiran)\
**Post date:** [October 21, 2026, 7:31pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/2 "2026-10-21T19:31:22Z")

</div>

> [@ichard26](#):
>
> I’ve been investigating alternative remedies for those unable to upgrade pip immediately. As far as I can tell, setting `PIP_CERT` to a valid CA bundle that has been patched is sufficient to stop pip from using the vendored `certifi` bundle.

The `PIP_CERT` trick does not work for me with pip 26.2.1.

```python
$ pip install -U pip==26.2.1
$ PIP_CERT=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem pip install -v emudhra --index-url https://dv-g3b-emudhra.we-have-a-problem.com/
Using pip 26.2.1 from /tmp/testenv/lib64/python3.12/site-packages/pip (python 3.12)
Looking in indexes: https://dv-g3b-emudhra.we-have-a-problem.com/
ERROR: Could not find a version that satisfies the requirement emudhra (from versions: none)
ERROR: No matching distribution found for emudhra

```

`legacy-certs` + `PIP_CERT` work:

```python
$ PIP_USE_DEPRECATED=legacy-certs PIP_CERT=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem pip install -v emudhra --index-url https://dv-g3b-emudhra.we-have-a-problem.com/
Using pip 26.2.1 from /tmp/testenv/lib64/python3.12/site-packages/pip (python 3.12)
Looking in indexes: https://dv-g3b-emudhra.we-have-a-problem.com/
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:998)'))': /emudhra/

```

`curl` and `uv` refuse a connection:

```python
$ curl --cacert /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem https://dv-g3b-emudhra.we-have-a-problem.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate
...
$ UV_SYSTEM_CERTS=1 uv pip install emudhra --index-url https://dv-g3b-emudhra.we-have-a-problem.com/
Using Python 3.12.14 environment at: /tmp/testenv
⠙ Resolving dependencies...                                                                                                                                                                                    
error: Failed to fetch: `https://dv-g3b-emudhra.we-have-a-problem.com/emudhra/`
  Caused by: error sending request for url (https://dv-g3b-emudhra.we-have-a-problem.com/emudhra/)
  Caused by: client error (Connect)
  Caused by: invalid peer certificate: UnknownIssuer

```

---

<div class="post-metadata">

**Author:** ![ichard26](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ichard26/32/5810_2.png) [@ichard26](https://discuss.python.org/u/ichard26)\
**Post date:** [October 21, 2026, 7:47pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/3 "2026-10-21T19:47:43Z")

</div>

I really wish I knew why `PIP_CERT` isn’t sufficient on Python 3.10+ for you, but I honestly do not know. Are you sure that pip isn’t caching the index pages? Recent versions of pip respect the cache control header for simple API index page responses. Try setting `PIP_NO_CACHE=1` or `pip cache purge`.

I’m glad the `legacy-certs` and `PIP_CERT` combo does work, though.

Edit: the cache shouldn’t… matter? or at least I’d expect the other commands to work if it were cached, but TBH, I am exhausted and not of the mental clarity needed to look further. I’ll update my comment to recommend `PIP_USE_DEPRECATED` for now.

---

<div class="post-metadata">

**Author:** ![tiran](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tiran/32/48_2.png) [@tiran](https://discuss.python.org/u/tiran)\
**Post date:** [October 21, 2026, 8:39pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/4 "2026-10-21T20:39:12Z")

</div>

> [@ichard26](#):
>
> I really wish I knew why `PIP_CERT` isn’t sufficient on Python 3.10+ for you, but I honestly do not know. Are you sure that pip isn’t caching the index pages? Recent versions of pip respect the cache control header for simple API index page responses. Try setting `PIP_NO_CACHE=1` or `pip cache purge`.

@ichard26 Your recommendation does not work because pip always loads `certifi` certificates in addition to system certs from `truststore`. `PIP_CERT` or `--cert` do not change the code path. Only `legacy-certs` trigger a different path that does not load `certifi`. I explained the issue in my initial comment [Information for Python users regarding eMudhra / emSign CA certificate security incident - #3 by tiran](https://discuss.python.org/t/information-for-python-users-regarding-emudhra-emsign-ca-certificate-security-incident/109576/3)

You can easily reproduce the problem by either `strace`-ing the process or by making the `cacert.pem` unreadable:

```python
$ chmod 0 /tmp/testenv/lib/python3.14/site-packages/pip/_vendor/certifi/cacert.pem
$ PIP_CERT=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem pip install emudhra --index-url https://dv-g3b-emudhra.we-have-a-problem.com/
WARNING: There was an error checking the latest version of pip.
ERROR: Exception:
Traceback (most recent call last):
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/base_command.py", line 109, in _run_wrapper
    status = _inner_run()
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/base_command.py", line 102, in _inner_run
    return self.run(options, args)
           ~~~~~~~~ ^^^^^^^^^^^^^^^
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/req_command.py", line 106, in wrapper
    return func(self, options, args)
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/commands/install.py", line 431, in run
    session = self.get_default_session(options)
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/index_command.py", line 80, in get_default_session
    self._session = self.enter_context(self._build_session(options))
                                       ~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/index_command.py", line 99, in _build_session
    ssl_context = _create_truststore_ssl_context()
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_internal/cli/index_command.py", line 50, in _create_truststore_ssl_context
    ctx.load_verify_locations(certifi.where())
    ~~~~~~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^
  File "/tmp/testenv/lib64/python3.14/site-packages/pip/_vendor/truststore/_api.py", line 160, in load_verify_locations
    return self._ctx.load_verify_locations(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
        cafile=cafile, capath=capath, cadata=cadata
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
PermissionError: [Errno 13] Permission denied

```

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [October 21, 2026, 9:04pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/5 "2026-10-21T21:04:01Z")

</div>

> [@tiran](#):
>
> Your recommendation does not work because pip always loads `certifi` certificates in addition to system certs from `truststore`. `PIP_CERT` or `--cert` do not change the code path. Only `legacy-certs` trigger a different path that does not load `certifi`.

Yes, over at pip we need to reassess our defaults, what options we provide to users, fix some documentation, and fix some bugs. We’re starting on [Switch to using truststore only by default (currently mixed certifi + truststore) · Issue #14374 · pypa/pip · GitHub](https://github.com/pypa/pip/issues/14374), but I’m sure there will be several follow up issues and PRs.

For now, as you say, if you want to enforce your cert bundle only you can run:

```sh
--use-deprecated=legacy-certs --cert bundle.pem

```

To set this legacy-certs mode in an env you can run:

```sh
# Unix-like:
export PIP_USE_DEPRECATED=legacy-certs 

# Powershell:
$env:PIP_USE_DEPRECATED = "legacy-certs"

# CMD:
set "PIP_USE_DEPRECATED=legacy-certs"

```

To set it via configuration you can run:

```python
pip config --user set global.use-deprecated legacy-certs

```

To give a little bit of a defense as to how we got here, a lot of the choices were made to encourage people to start using SSL verification without breaking their existing setups. It was not that long ago that every guide I read inside a large organization included something like `--trusted-host pypi.org --trusted-host files.pythonhosted.org` for running any pip command, completely disabling verification.

---

<div class="post-metadata">

**Author:** ![teward](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/teward/32/38234_2.png) [@teward](https://discuss.python.org/u/teward)\
**Post date:** [October 22, 2026, 2:02pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/6 "2026-10-22T14:02:41Z")

</div>

**(For Linux systems)**

Correct me if I’m wrong, but can’t this be overridden with `PIP_CERT` env vars, the `--cert` option to `pip`, or in the configuration file for `pip` by defining this? (at least on Linux systems)

```plaintext
[global]
cert = /etc/ssl/certs/ca-certificates.crt

```

From what I can tell this would override the certificate bundle as well.

(For what it’s worth, I fully agree with switching to using `truststore` by default - modified versions of `pip` and `certifi` do this in Ubuntu and Debian distributions when installed via `apt` to enforce the use of the system CA certificates store - though that’s not as relevant to Python and `pip` itself, it makes more sense to at least note it)

---

<div class="post-metadata">

**Author:** ![tiran](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tiran/32/48_2.png) [@tiran](https://discuss.python.org/u/tiran)\
**Post date:** [October 22, 2026, 2:59pm UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/7 "2026-10-22T14:59:06Z")

</div>

> [@teward](#):
>
> Correct me if I’m wrong, but can’t this be overridden with `PIP_CERT` env vars, the `--cert` option to `pip`, or in the configuration file for `pip` by defining this? (at least on Linux systems)
> 
> From what I can tell this would override the certificate bundle as well.

No, it does not disable the certificate bundle. The default code path always injects the CA bundle from vendored `certifi`. You also have to enable the `legacy-cert` code path to disable `ctx.load_verify_locations(certifi.where())`.

> <https://github.com/pypa/pip/blob/26.2.2/src/pip/_internal/cli/index_command.py#L49-L50>

> <https://github.com/pypa/pip/blob/26.2.2/src/pip/_internal/cli/index_command.py#L98-L101>

---

<div class="post-metadata">

**Author:** ![teward](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/teward/32/38234_2.png) [@teward](https://discuss.python.org/u/teward)\
**Post date:** [October 23, 2026, 3:21am UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/8 "2026-10-23T03:21:46Z")

</div>

@tiran Hmm, that explains some things there. Assuming this is done via either `--use-deprecated=legacy-cert` or similar config files in the pip config file, then the system CA cert bundle (inherited by default I presume from base SSL contexts) would be used, in addition to `--cert` defined certificates?

---

<div class="post-metadata">

**Author:** ![tiran](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tiran/32/48_2.png) [@tiran](https://discuss.python.org/u/tiran)\
**Post date:** [October 23, 2026, 6:27am UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/9 "2026-10-23T06:27:42Z")

</div>

@teward It is … complicated. `ssl.SSLContext()` does not load any certificates from the system trust store. Many years ago I introduced `ssl.create_default_context()`. That function returns an `SSLContext` with trust anchors from the system trust store.

But there is an additional catch! OpenSSL has hard-coded path to cert PEM bundle and directory to hashed CA cert files. Linux distributions do not agree on the location of the CA certs. Alpine, Debian/Ubuntu, Fedora/CentOS/RHEL, Gentoo, SuSE … they all disagree and compile OpenSSL with different paths. Here is where `truststore` package comes into play. It checks [multiple locations](https://github.com/sethmlarson/truststore/blob/main/src/truststore/_openssl.py).

Most recent Linux distros use `/etc/ssl/cert.pem`. For example I added a cross-distro compatibility feature to Fedora, CentOS, and RHEL in 2020, [1895619 – Symlink cert.pem and openssl.cnf to /etc/ssl for cross-distro compatibility](https://bugzilla.redhat.com/show_bug.cgi?id=1895619) . Fedora 44 removed the link for a brief moment during its development cycle.

---

<div class="post-metadata">

**Author:** ![GalaxySnail](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/galaxysnail/32/5655_2.png) [@GalaxySnail](https://discuss.python.org/u/GalaxySnail)\
**Post date:** [October 23, 2026, 6:49am UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/10 "2026-10-23T06:49:21Z")

</div>

> [@tiran](#):
>
> But there is an additional catch! OpenSSL has hard-coded path to cert PEM bundle and directory to hashed CA cert files. Linux distributions do not agree on the location of the CA certs. Alpine, Debian/Ubuntu, Fedora/CentOS/RHEL, Gentoo, SuSE … they all disagree and compile OpenSSL with different paths. Here is where `truststore` package comes into play. It checks [multiple locations](https://github.com/sethmlarson/truststore/blob/main/src/truststore/_openssl.py).

If I understand correctly, is `ssl.create_default_context()` just enough if I’m not building my own OpenSSL from source? Since the stdlib `ssl` links against the system OpenSSL library, I would expect the certificate paths to be correct. Is that right?

---

<div class="post-metadata">

**Author:** ![tiran](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tiran/32/48_2.png) [@tiran](https://discuss.python.org/u/tiran)\
**Post date:** [October 23, 2026, 6:54am UTC](https://discuss.python.org/t/avoiding-compromised-certificates-in-pip/109602/11 "2026-10-23T06:54:24Z")

</div>

> [@GalaxySnail](#):
>
> If I understand correctly, is `ssl.create_default_context()` just enough if I’m not building my own OpenSSL from source? Since the stdlib `ssl` links against the system OpenSSL library, I would expect the certificate paths to be correct. Is that right?

Yes – assuming your OpenSSL is correctly configured, you are not on macOS, and you don’t copy your binaries to a machine with a different Linux distribution.
