Last reviewed: November 4, 2026 (Asia/Shanghai); sources fetched November 3 UTC.
Direct answer
Preserve TLS certificate verification in a coding-agent patch by requiring an approved certificate chain and the expected server identity on every outbound connection. Keep the client’s secure defaults, assign an owner to its trust store, and treat verification failure as a terminal connection failure. Repair the certificate, hostname, clock, or approved CA configuration before retrying. Remove fallbacks that disable verification or switch to plaintext.
A common trigger is an agent fixing CERTIFICATE_VERIFY_FAILED by adding verify=False. The Requests Advanced Usage guide
explicitly says this accepts any server certificate and ignores hostname mismatches and expired certificates. A successful request after that change no longer demonstrates the original authentication requirement. MITRE CWE-295
describes how improper certificate validation can let a malicious host impersonate a trusted entity.
The review target is the effective client configuration, including wrappers, adapters, environment settings, and error handlers. A secure default at construction can be weakened later. The workflow below supplies acceptance criteria for reviewing those changes; its example does not establish the security of a deployment.
Who this is for
Use this guide when an agent changes an HTTPS API client, artifact downloader, webhook sender, package helper, or internal service connector. It is especially useful when a patch adds private-CA support, replaces an HTTP library, changes a container image, or catches TLS exceptions.
Pair peer authentication with the site’s SSRF review for URL fetchers , which governs destination authority, and sandbox egress allowlist guidance , which governs reachable routes. An authenticated public server can still be outside the task’s permitted destinations.
Key takeaways
- Verify both the certificate chain and the intended hostname. A chain check alone leaves an identity gap.
- Treat CA bundles as deployment policy. Request data and downloaded peer certificates must not choose new trust anchors.
- Review the library’s actual trust source; Python’s default context and Requests do not necessarily load the same roots.
- Exercise certificate failures through the production client wrapper, including its retry and fallback branches.
- Keep diagnostics to stable failure classes and configuration revisions. Avoid dumping request data, environment values, or client private keys.
Contract details to verify
Write a short trust contract before accepting a connectivity repair:
| Decision | Required evidence |
|---|---|
| Expected peer | Approved service name and how it reaches hostname verification |
| Trust anchors | Default roots or a named CA bundle, its owner, and its revision |
| Configuration authority | Deployment-controlled settings; callers cannot disable checks |
| Client construction | Runtime and library versions, TLS context, adapters, and later overrides |
| Intermediaries | Whether an approved proxy terminates TLS and which peer the client authenticates |
| Failure behavior | Fixed error class, bounded attempt count, and no insecure recovery branch |
| Change lifecycle | How new trust material is reviewed, distributed, tested, and retired |
Search changed code and configuration for verify=False, CERT_NONE, check_hostname=False, custom SSL contexts, warning suppression, and catches of TLS exceptions. These are review leads: a test fixture may intentionally construct a bad peer. Trace which settings can reach the deployed client and who can select them.
Keep chain and hostname checks together
The Python ssl documentation
recommends create_default_context() for ordinary client use. It enables certificate validation and hostname checking. The same documentation explains that CERT_REQUIRED by itself does not match hostnames; client authentication of the server also needs the hostname check.
For a low-level Python connector, this small constructor preserves those defaults:
import ssl
def client_tls_context(ca_file: str | None = None) -> ssl.SSLContext:
return ssl.create_default_context(cafile=ca_file)
The ca_file argument comes from approved deployment configuration. With None, the function loads the default CA certificates. With a file path, it loads that supplied CA material. Python documents default-root loading when no cafile, capath, or cadata is supplied, so passing a private bundle must be a deliberate trust-store decision.
When the connector wraps its connected socket, pass the approved DNS name through server_hostname=approved_host. Preserve that logical name even if a separately reviewed connector controls the socket address. Changing the HTTP Host header cannot substitute for setting the TLS peer identity.
If policy requires adding a private CA while retaining default roots, construct a default context first and then call load_verify_locations(cafile=approved_ca_file) on it. Document that broader trust set. A private-service connector may instead need only its private CA. Choose according to the service contract and test the chosen roots explicitly.
Check the HTTP library’s trust path
Requests verifies server certificates by default and documents using certifi for its default CA certificates. Its verify argument can select a CA bundle, and a session can persist that setting. Its advanced guide also documents REQUESTS_CA_BUNDLE, with CURL_CA_BUNDLE as a fallback. Record which path the actual wrapper uses rather than assuming an operating-system trust-store change reaches every Python client.
Prepared requests deserve a separate check. Requests warns that the prepared-request flow does not automatically account for environment settings such as REQUESTS_CA_BUNDLE; its documented remedy explicitly merges the required environment settings. If a wrapper uses Session.send(), trace how its verification setting arrives there. A missed CA setting should lead to a configuration repair while validation stays enabled.
Review custom adapters and proxies too. For a TLS-inspecting proxy, Requests notes that the client must trust the proxy’s root certificate. That is an explicit expansion of trust requiring deployment ownership. Confirm where TLS terminates and what authenticates the onward connection. Do not let a connection error silently select a different proxy or trust bundle.
Happy-path operator workflow
- Identify the changed outbound operation, approved service name, and exact client factory used by the application.
- Trace the trust source through deployment configuration, session settings, prepared requests, adapters, and proxies. Record who can change each value.
- Preserve chain and hostname verification. Ensure application input cannot select an insecure mode or replace the approved CA file.
- Run a harmless successful handshake using a certificate signed by the intended test CA and valid for the expected test name.
- Run the failure matrix below through the same factory. Then exercise the application’s error handler and assert that it makes no insecure follow-up attempt.
- Retain runtime versions, trust-policy revision, fixture identifiers, outcomes, and attempt counts with the patch. Review the final diff for later overrides.
Keep the application’s existing time, response-size, and concurrency limits around this operation. The context constructor only establishes TLS settings; it does not implement those resource limits or destination authorization.
Error-path operator workflow
On a certificate-verification error, stop that connection attempt and return a fixed application failure such as upstream_tls_verification_failed. Preserve a bounded internal reason class so an operator can distinguish trust-chain, name, validity, and configuration problems. Do not expose raw exception text to callers or log complete URLs, headers, certificate-key files, or environment dumps.
Repair the responsible layer. An unexpected hostname needs endpoint or certificate correction. An incomplete chain needs server-side investigation. An approved private CA needs a reviewed bundle distributed to the client. A validity failure also warrants checking the system clock. A missing or malformed local CA file is a configuration failure that should stop client initialization.
The error branch must preserve the original verification requirement. A retry with verify=False, a hostname-check bypass, automatic trust of the presented certificate, or an HTTP URL changes that requirement. Keep those branches out of the production path. After a reviewed repair, make a new bounded attempt with verification enabled.
Test certificate failures without public probes
Create temporary test CAs and leaf certificates in a disposable fixture directory. Use a reserved name such as fixture.invalid, temporary private keys, and inert application data. Python’s MemoryBIO interface lets a harness exchange TLS records in memory without binding a listening socket or contacting a public service.
| Fixture | Expected outcome |
|---|---|
| Trusted issuer, valid dates, matching name | Handshake completes; inert application data can pass |
| Same server certificate with only an unrelated test root trusted | Verification fails |
| Trusted certificate with a different requested hostname | Verification fails |
| Expired leaf signed by the trusted test CA | Verification fails |
| Missing configured CA file | Initialization fails; no fallback context |
| Malformed configured CA file | Initialization fails; no fallback context |
| Verification failure passed through the application wrapper | Fixed failure and no request through an insecure alternative |
A disposable in-memory harness exercised the displayed constructor on Python 3.12.3 with OpenSSL 3.0.13. Seven tests passed: secure default flags, a valid handshake and inert data transfer, rejection of an unrelated issuer, rejection of a hostname mismatch, rejection of an expired leaf, and failures for missing and malformed CA files. The tests generated temporary certificates and made no network connections.
That harness checks the constructor and TLS handshake. The application-wrapper row remains an acceptance test for the repository under review, along with Requests adapters, proxy termination, redirects, connection pooling, and trust-store rotation. Run those checks in the application’s actual client stack. A mocked SSLError can test error mapping, but real certificate fixtures are needed to exercise verification itself.
Failure modes
The incident workaround becomes a persistent client option. An insecure development flag reaches deployment through a shared factory or configuration default. Remove that option from the deployed interface and assert the effective settings in the integration test.
The patch trusts whatever certificate the failed peer presents. That promotes unverified network input into authority. Obtain approved CA material through the deployment’s established review and distribution path.
A chain check passes while hostname checking is disabled. The certificate can be valid for another identity. Retain the expected name and include the wrong-hostname fixture.
The CA fix reaches one client but misses another. A shell tool, Python default context, and Requests wrapper can have different trust sources. Exercise each changed production path with the same intended issuer and rejection cases.
Warnings disappear and the patch is declared fixed. Warning suppression changes diagnostics. Inspect verification settings and handshake outcomes directly.
A pooled connection hides a trust change. Reusing an established connection can avoid a new handshake. For trust-store and rotation tests, create fresh client contexts and connections, then separately review how long existing connections may remain usable.
FAQ
Does a client certificate replace server verification?
No. Requests documents client certificates through cert, separately from server verification through verify. A client certificate authenticates the client in a configuration that requires it; the client still needs to authenticate the server. Keep client private keys outside repository fixtures and diagnostic output.
Should the patch fix this by pinning the peer certificate?
Pinning introduces a separate identity and rotation policy. MITRE’s mitigation guidance says relevant certificate properties, including the hostname, must be fully validated before pinning. Review that lifecycle explicitly rather than learning a permanent pin from a failed connection.
Do secure defaults guarantee certificate-revocation checking?
No. Python documents that OpenSSL does not require or verify certificate revocation lists by default. If revocation enforcement is a requirement, choose and test a client configuration that implements the required policy. Do not infer it from a successful chain-and-hostname test.
Sources checked
Three primary references were fetched successfully on November 3, 2026 UTC:
- Python
ssldocumentation , published by the Python Software Foundation, documents default contexts, trust loading, hostname checks,CERT_REQUIRED, client socket wrapping,MemoryBIO, and the limits of default CRL checking. The fetched page identifies Python 3.15.2. Local tests used the older runtime identified above. - Requests Advanced Usage
, published by the Requests project, documents verification defaults and bypass consequences, custom CA bundles, prepared-request environment handling, proxies,
certifi, and client certificates. The fetched documentation identifies Requests 2.34.2. - MITRE CWE-295: Improper Certificate Validation , published by the MITRE CWE Program, defines the weakness, impersonation consequences, and certificate-management and pinning mitigations. The fetched entry reports an October 28, 2026 update.
These maintained technical references support an evergreen patch-review task. The trust contract, failure policy, and test matrix are engineering recommendations; the local harness result covers only the stated fixtures and runtime.
Reader next step
Choose one HTTPS client changed by the next coding-agent patch. Record its expected peer, effective CA source, hostname-verification setting, and certificate-failure branch. Add a valid local control plus unrelated-issuer, wrong-hostname, and expired-certificate fixtures. Keep the patch open until the real wrapper rejects those peers without making an insecure follow-up attempt.