ASVS 5.0 V16.3.4 requires that the application writes a log entry whenever something unexpected breaks or a security control fails. A backend TLS failure is the example the standard gives. This is a Level 2 requirement.
In plain words: if a certificate check fails, an authorization library throws, or a database driver returns an error nobody planned for, there must be a log record. Silence is the failure mode this requirement targets.
Why it matters
Attackers probe. A burst of TLS errors to one backend, a spike in signature verification failures, or a parser exception that appears only after a strange request are all early signals. If nothing is logged, the first sign of trouble is the incident itself.
Security control failures are also a quiet way to lose protection. A service that retries without verifying a certificate after a handshake error has failed open, and nobody will know unless the failure was recorded.
How to implement it
Decide which events count. At minimum: TLS or mTLS handshake failures to backends, token or signature validation errors, authorization framework exceptions, and any exception caught by a generic handler.
Log the event type, a timestamp, the component, the target host (not credentials), and a correlation id. Do not log secrets or full request bodies.
import logging, ssl, socket
log = logging.getLogger("security")
def call_backend(host, port, ctx):
try:
with socket.create_connection((host, port), timeout=5) as raw:
with ctx.wrap_socket(raw, server_hostname=host) as tls:
return tls.version()
except ssl.SSLError as e:
log.error("backend_tls_failure host=%s port=%s reason=%s", host, port, e.reason)
raise
except OSError as e:
log.error("backend_connect_failure host=%s port=%s err=%s", host, port, type(e).__name__)
raise
Re-raise after logging so the caller still fails closed. Logging is not handling.
How to test it
Point the service at a backend with an expired or self signed certificate and make a request that uses it. Then search the log for the event. Repeat with a hostname mismatch. Also force an unexpected exception, for example by stubbing a dependency to raise, and confirm an entry appears with a correlation id.
Make this an automated integration test so a refactor cannot silently remove the log line.
What counts as a security control failure
Teams often log exceptions but miss the quieter failures. A control failure is any moment where a protection did not do its job or could not run. Examples: the certificate chain did not validate, a JWT signature check rejected a token, a CSRF check failed, a rate limiter could not reach its store, the malware scanner timed out, a key management call was denied.
Give these events a stable name and a consistent field layout, such as event=control_failure control=backend_tls target=payments-api. Stable names let detection rules match them without regular expressions that break when someone rewords a message.
Volume and noise
Logging every failure at error level can bury the signal. Keep one record per event but add counters and rate based alerts. A single expired certificate on a staging host is a ticket. Two hundred handshake failures to production in a minute is a page. Deduplicate noisy repeats at the log platform, not at the source, so the evidence is still complete.
Common mistakes
- Catching an exception and returning a default without logging.
- Logging only at debug level, which production turns off.
- Logging the error but also disabling verification to retry.
- Including the full URL with query string tokens in the message.
Quick check
Should the service continue after a TLS failure once it has logged it?
No. Log it, then fail the operation. Logging records the failure; it does not authorize carrying on.