| name | design-error-handling |
| description | Use when designing how errors, exceptions, and failures are surfaced to users and logged internally — ensuring error messages don't leak sensitive implementation details while providing enough context for debugging. |
| source | OWASP Error Handling Cheat Sheet (owasp.org/www-project-cheat-sheets); OWASP Top 10 2021 A05; CWE-209; NIST SP 800-53 SI-11 |
| tags | ["security","owasp","error-handling","information-disclosure","logging","developer"] |
Design Error Handling
Return generic error messages to clients while logging detailed errors server-side — preventing stack traces, database schemas, and internal paths from leaking to attackers.
Why This Is Best Practice
Adopted by: OWASP Top 10 2021 A05 (Security Misconfiguration) explicitly calls out verbose error messages as a configuration failure. CWE-209 (Generation of Error Message Containing Sensitive Information) is tracked across hundreds of CVEs annually. NIST SP 800-53 SI-11 mandates error handling that reveals minimal information. Django's DEBUG = False requirement, Rails' config.consider_all_requests_local = false, and Spring's production error configuration all implement this principle.
Impact: Error messages containing stack traces reveal framework versions (enabling targeted CVE exploitation), internal file paths (useful for path traversal), database table names and column names (enabling refined SQL injection), and internal IP addresses. The 2014 Heartbleed exploit was partly facilitated by verbose error output during discovery. Shodan scans routinely find live debug error pages that reveal full stack traces including database credentials in query parameters.
Why best: Catching and swallowing all exceptions silently (the alternative) creates debugging nightmares and hides operational issues. The correct design separates external-facing error messages (generic, safe) from internal logging (detailed, not user-facing) — giving both security and debuggability.
Sources: OWASP Error Handling Cheat Sheet; CWE-209; NIST SP 800-53 SI-11; Django/Rails production configuration guides
Steps
-
Define safe, generic error responses for clients:
ERROR_MESSAGES = {
'not_found': 'The requested resource was not found.',
'unauthorized': 'Authentication required.',
'forbidden': 'You do not have permission to perform this action.',
'validation': 'The request contains invalid data.',
'server_error': 'An unexpected error occurred. Please try again.',
'rate_limited': 'Too many requests. Please wait before retrying.',
}
@app.errorhandler(Exception)
def handle_exception(e):
error_id = generate_error_id()
logger.exception("Unhandled exception [%s]", error_id, exc_info=e)
if isinstance(e, NotFound):
return jsonify({'error': ERROR_MESSAGES['not_found'],
'error_id': error_id}), 404
if isinstance(e, Unauthorized):
return jsonify({'error': ERROR_MESSAGES['unauthorized'],
'error_id': error_id}), 401
return jsonify({'error': ERROR_MESSAGES['server_error'],
'error_id': error_id}), 500
The lets users report the ID to support, who can correlate to full server logs — without exposing the details publicly.
Rules
error_id (a UUID) in the client response enables support correlation without exposing internals.
- HTTP 200 with
{"success": false, "error": "..."} bodies break client error handling — use proper status codes.
- Error messages must not differ between "user not found" and "wrong password" — both return 401 with identical messaging to prevent user enumeration.
- Logging exceptions without re-raising them in middleware swallows errors; use
logger.exception() which logs the full traceback.
Common Mistakes
except Exception as e: return str(e) — the single most common source of information leakage in web apps.
- Different error messages for "user not found" vs "wrong password" — enables user enumeration (attacker learns which usernames exist).
- Logging PII in error context — stack traces that include the request body may log passwords, SSNs, or payment data.
- Catching BaseException — catches
KeyboardInterrupt, SystemExit — use Exception as the catch-all base class.