mishandling-exceptional-conditions
Detect, analyze, and remediate OWASP A10:2025 — Mishandling of Exceptional Conditions in Python web applications (FastAPI and Flask). Use this skill whenever the user asks about error handling security, exception management, fail-open vulnerabilities, stack trace exposure, uncaught exceptions, transaction rollback safety, or resource leaks in exception paths. Also trigger for any OWASP A10 audit, security code review involving try/except blocks, global exception handlers, or HTTP error responses that may leak sensitive information. If the user mentions CWE-209, CWE-248, CWE-636, CWE-703, CWE-754, or CWE-476 in a Python context, use this skill immediately.
OWASP A10:2025 — Mishandling of Exceptional Conditions
Brand new category for 2025, replacing SSRF (absorbed into A01). Contains 24 CWEs with 769,581 recorded occurrences. Addresses programs that fail to prevent, detect, and respond to unusual situations — leading to crashes, unexpected behaviour, information disclosure, and exploitable states.
Core Concepts
What It Covers
| Weakness | Risk |
|---|---|
| Improper error handling | Unhandled exceptions crash the app or expose internals |
| Failing open (CWE-636) | Auth/authz checks that pass when an exception is thrown |
| Sensitive data in error messages (CWE-209) | Stack traces, DB details, file paths leaked to clients |
| Uncaught exceptions (CWE-248) | No global safety net → 500 with debug output |
| Resource exhaustion in exception paths | DB connections, file handles never closed on error |
| State corruption | Partial transactions committed on exception |
| NULL pointer / None dereference (CWE-476) | Missing guard for None before attribute access |
| Missing default cases (CWE-754) | if/elif chains with no else; match with no wildcard |
| Race conditions in error recovery | Cleanup logic itself can be interrupted |
Prevention Principles
- Catch errors where they occur — at the function that can handle them, not three layers up.
- Fail closed — when auth/authz logic throws, deny access; never pass through.
- Roll back completely — partial DB transactions must be rolled back, never committed.
- Separate user messages from technical details — log the stack trace server-side; return a safe, generic message to the client.
- Global exception handler as safety net — every app needs one, but it is not a substitute for local handling.
- Rate-limit and quota — prevent exceptional conditions (e.g., resource exhaustion) from being triggered intentionally.
FastAPI Testing Checklist
1. Global Exception Handler
# REQUIRED — catches anything not handled locally
@app.exception_handler(Exception)
async def global_handler(request: Request, exc: Exception):
logger.error("Unhandled exception", exc_info=exc)
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
Flag if absent. The default FastAPI 500 response may include traceback in development mode.
2. Fail-Closed Auth Middleware (CWE-636)
# BAD — exception causes pass-through (fail open)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
try:
verify_token(request)
except Exception:
pass # ← VULNERABILITY
return await call_next(request)
# GOOD — exception causes denial (fail closed)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
try:
verify_token(request)
except Exception as e:
logger.warning("Auth check failed", exc_info=e)
return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
return await call_next(request)
3. Custom HTTP Error Responses (CWE-756 / CWE-209)
# Register handlers for all common error codes
@app.exception_handler(404)
async def not_found(request, exc):
return JSONResponse(status_code=404, content={"detail": "Not found"})
@app.exception_handler(422)
async def validation_error(request, exc):
return JSONResponse(status_code=422, content={"detail": "Invalid input"})
Flag any handler that returns str(exc), repr(exc), or traceback.format_exc() in the response body.
4. Resource Cleanup in Exception Paths
# BAD — connection leaked if query raises
conn = db.connect()
result = conn.execute(query) # raises → conn never closed
conn.close()
# GOOD — context manager guarantees cleanup
with db.connect() as conn:
result = conn.execute(query)
Scan for: db.connect(), open(file), socket.socket() not inside a with block or try/finally.
5. SQLAlchemy Transaction Rollback
# BAD — partial write committed on error
session.add(obj)
risky_operation() # raises → session still commits elsewhere
session.commit()
# GOOD — rollback on exception
try:
session.add(obj)
risky_operation()
session.commit()
except Exception:
session.rollback()
raise
Flag: any session.commit() not paired with session.rollback() in an except branch.
Flask Testing Checklist
1. Global Exception Handler
@app.errorhandler(Exception)
def global_error(e):
app.logger.error("Unhandled exception", exc_info=e)
return {"error": "Internal server error"}, 500
Also register specific handlers:
@app.errorhandler(404)
def not_found(e):
return {"error": "Not found"}, 404
@app.errorhandler(500)
def server_error(e):
return {"error": "Server error"}, 500
2. Flask Debug Mode — Doubles as A02 + A10 Issue
Debug mode (debug=True or FLASK_DEBUG=1) enables the Werkzeug interactive debugger, which:
- Exposes full tracebacks to the browser.
- Allows arbitrary code execution via the debugger console.
Flag immediately if found in production config or environment variables.
3. request.args.get() Silent None Returns
# BAD — returns None silently; downstream code may crash
user_id = request.args.get("user_id")
user = db.query(User).filter_by(id=user_id).first() # id=None → wrong query
# GOOD — explicit validation
user_id = request.args.get("user_id")
if user_id is None:
abort(400, description="user_id is required")
FastAPI's Pydantic models prevent this automatically; Flask requires manual guards.
4. Fail-Closed before_request Hooks
# BAD — exception in auth check bypasses protection
@app.before_request
def check_auth():
try:
verify_session()
except:
pass # ← VULNERABILITY
# GOOD
@app.before_request
def check_auth():
try:
verify_session()
except Exception as e:
app.logger.warning("Session verify failed", exc_info=e)
abort(401)
Automated Scanning Approach
Static Analysis (Bandit)
bandit -r . -t B110,B112 # try-except-pass and try-except-continue
bandit -r . --severity-level medium
Key Bandit test IDs for this category:
B110—try: ... except: pass(swallowed exception)B112—try: ... except: continue(swallowed in loop)
Pattern Search (grep / ripgrep)
# Fail-open patterns
rg "except.*:\s*(pass|continue|return True)" --type py
# Stack traces leaking to response
rg "traceback\.format_exc\(\)|str\(exc\)|repr\(exc\)" --type py
# Missing context managers
rg "open\(|\.connect\(\)" --type py # review each hit for 'with'
# Missing rollback
rg "session\.commit\(\)" --type py # check each for paired rollback
Runtime / DAST
- Trigger deliberate 404, 500, and validation errors (422 on FastAPI).
- Inspect HTTP responses for presence of: file paths, stack frames, SQL query strings, exception class names.
- Probe middleware by sending a malformed Authorization header — verify the response is
401, not200.
Key CWEs (A10:2025)
| CWE | Name | Python Manifestation |
|---|---|---|
| CWE-209 | Sensitive Info in Error Messages | str(exc) in JSON response, debug traceback |
| CWE-248 | Uncaught Exception | No global handler; unhandled raise crashes app |
| CWE-636 | Failing Open | except: pass in auth check |
| CWE-703 | Improper Handling of Exceptional Conditions | Broad except that hides errors |
| CWE-754 | Improper Check for Unusual Conditions | Missing None/empty checks on external input |
| CWE-476 | NULL Pointer Dereference | Attribute access on potentially-None object |
| CWE-756 | Missing Custom Error Page | Default framework error pages with debug info |
Remediation Priority
- Critical: Fail-open auth middleware /
before_requesthooks (CWE-636) — direct authentication bypass. - Critical: Debug mode enabled in production (Werkzeug debugger = RCE).
- High: Stack traces / DB details in HTTP responses (CWE-209) — aids attacker reconnaissance.
- High: Missing global exception handler — unhandled paths crash app and may reveal details.
- Medium: Missing transaction rollback — data integrity corruption.
- Medium: Resource leaks in exception paths — connection pool exhaustion under attack.
- Low: Silent
Nonereturns fromrequest.args.get()— logic bugs, not directly exploitable.