TLS and the common risks
Know what an encrypted connection promises, keep its checks switched on, and use a shared list of common risks to review a design.
BY THE END, YOU CAN
- Say what TLS guarantees and what it leaves to the application
- Keep certificate and hostname verification on, and spot code that turns it off
- Use the OWASP Top 10 as a map for reviewing a service
Diagram description: Client → api.relay.example (TLS: private, unaltered); Attacker in the middle → Client; api.relay.example → Authorization; api.relay.example → Stored data
What TLS promises
TLS, Transport Layer Security, is the protocol under HTTPS and under most connections to databases and email providers.
Its specification, RFC 8446 for TLS 1.3, gives the channel three properties. Authentication: the server always proves who it is, and the client can too. Confidentiality: only the two ends can read what is sent.
Integrity: an attacker cannot change the data without being detected. The server proves its identity with a certificate, which binds a name such as api.relay.example to a public key and is signed by a certificate authority the client already trusts; during the handshake the server shows it holds the matching private key. TLS protects data while it travels and nothing more.
It does not protect the copy in your database or your logs, it does not decide whether the authenticated user may cancel that job, and it does not hide that a connection to your server took place.
Verification is what people switch off
Encryption without verification only encrypts to whoever answers.
If the client accepts any certificate, an attacker on the network path can present their own, read everything, and pass it along. Python's ssl.create_default_context() loads the system's trusted certificate authorities and turns on certificate validation and hostname checking; since Python 3.10 it also requires TLS 1.2 or later. A context built by calling the SSLContext constructor directly, by contrast, may verify nothing.
Verification is usually switched off in a hurry: verify=False in an HTTP client, CERT_NONE, or check_hostname set to False, added to get past a certificate error in a test environment and then shipped. Fix the cause instead: give that environment's certificate authority to the client with cafile, and keep the checks on. Search for those strings in every review.
For a service that browsers use, also send the Strict-Transport-Security header, which tells browsers to use only HTTPS for your site for the time it states.
import ssl
import urllib.request
context = ssl.create_default_context() # trusted CAs, certificate and hostname checks
with urllib.request.urlopen("https://example.com", context=context) as response:
print(response.status)
# A test environment with its own certificate authority: trust it, keep the checks.
staging = ssl.create_default_context(cafile="staging-ca.pem")
A map of common risks
OWASP, an open community for application security, publishes the OWASP Top 10, a list of common web application risks; the 2025 edition is current.
Read it as a map that sends you back to what you already know. A01, Broken Access Control: the ownership check from the lesson on trust boundaries. A02, Security Misconfiguration: verification switched off, debug pages left on, default passwords.
A03, Software Supply Chain Failures: dependencies you never checked. A04, Cryptographic Failures: fast password hashes, unverified TLS. A05, Injection: SQL built by formatting strings instead of binding parameters.
A06, Insecure Design: risks no threat model looked for. A07, Authentication Failures: weak password storage and login handling. A08, Software or Data Integrity Failures: trusting code or data whose origin was never checked.
A09, Security Logging and Alerting Failures: attacks that nobody sees. A10, Mishandling of Exceptional Conditions: errors that leave the system open instead of closed. A short list cannot cover everything, but walking a design through it catches the usual mistakes.
COMMON FAILURE MODE
Turning off certificate verification to get past an error in a test environment, then shipping it. The traffic is still encrypted, but to whoever answered.
TRY IT YOURSELF
Search Relay, or another codebase you know, for verify=False, CERT_NONE, check_hostname = False, and http:// addresses. For each hit, say who could sit in the middle and what the fix is. Then walk Relay's design through the ten OWASP categories and note one risk for each.
Make the call in the laboratory.
The two-question checkpoint, your notes, and your saved progress are in the laboratory. Free, with no account.
Open in the laboratory ↗CONNECT IT TO RELAY
Serve Relay's API over HTTPS with a certificate, send Strict-Transport-Security, and make every outgoing call (the database, the email provider) use a verifying TLS context. Add a check to code review for verify=False and its relatives, and write Relay's OWASP Top 10 walk-through in its threat model.