Certificate trust and corporate CA interception
Summary: HTTPS clients only trust certificates signed by a CA in their own trust store. A corporate proxy that inspects traffic re-signs everything with a private CA, so clients that don't know that CA reject every connection. The fix is to trust that CA — not to stop verifying.
How certificate trust works
- A CA (certificate authority) is an organisation that signs certificates — a passport office for websites.
- On every HTTPS call the client checks the server's certificate was signed by a CA in its trust store. No match, no connection.
- Each runtime has its own trust store. Python uses
certifi(~150 CAs), Node has its own bundle, browsers and the OS have theirs. Installing a certificate in one does not install it in the others.
Why corporate networks break it
To inspect encrypted traffic, a proxy has to decrypt it — and to decrypt it, it must terminate the TLS connection itself and re-encrypt with its own certificate. Every response therefore arrives signed by the company's private CA instead of the real site's.
IT pushes that CA into the OS trust store, so browsers work and nothing looks broken.
Anything with its own store — Python, Node, Java, Docker builds, git — still rejects it,
which is why the failure shows up only in code.
What to do
| Option | How | When |
|---|---|---|
| Skip verification | SSL_VERIFY=false | Dev only — accepts any certificate |
| Point at the CA file | SSL_CERT_FILE=/path/ca.pem (or REQUESTS_CA_BUNDLE) | The correct per-app fix |
| Add the CA to the trust store | install into the OS and certifi once | Best long-term — every app benefits |
Skipping verification doesn't just accept the proxy's certificate, it accepts anyone's — which is the attack TLS exists to prevent. Never let it default to off, and never ship it. Make it an explicit environment variable so production fails closed.
Where this came up
Every outbound HTTPS call from the Data Patch Agent failed certificate verification on the
agency network. SSL_VERIFY was hardcoded False as a workaround; it's now env-configurable
— .env sets false locally, production omits the var and defaults to True.
This is outbound traffic — the app calling out. The inbound counterpart is exposing localhost with a tunnel.