Empty catch blocks and unsafe failure handling¶
Catching an exception without an appropriate outcome can hide a failed operation. It becomes a security issue when the application continues with an unsafe default, skips a required check, or reports success for work that did not complete. An empty catch block is not automatically exploitable: an expected, deliberately ignored condition may be valid when the resulting state is explicitly safe.
Trust boundary: failure to obtain a security decision must not be interpreted as permission. Adding a print or log statement alone does not fix the control flow, and echoing the raw exception to a client can introduce information disclosure.
Unsafe example: an unavailable policy permits access¶
This Python example assumes load_policy returns an application-owned object whose allows method evaluates an authenticated actor's access to a document.
def can_read(load_policy, actor, document_id):
allowed = True
try:
allowed = load_policy().allows(actor, document_id)
except OSError:
pass
return allowed
An I/O failure leaves allowed true. Logging the exception and then returning the same value would still grant access.
Safer example: stop on an unavailable decision¶
class AuthorizationUnavailable(RuntimeError):
pass
def can_read(load_policy, actor, document_id, logger):
try:
policy = load_policy()
return policy.allows(actor, document_id)
except OSError:
logger.error('authorization_policy_unavailable')
raise AuthorizationUnavailable('Authorization is unavailable') from None
The logger records a fixed event without the raw exception or document contents. It should handle its own delivery failures according to an operational policy. The request boundary maps AuthorizationUnavailable to a generic service-unavailable response and performs no protected read. An explicit false policy result remains denial. Unexpected exception types propagate to the application's controlled error handler; they must not trigger an allow-by-default fallback.
This scoped interface requires allows to return a boolean and the caller to use it as the authorization result. Validate that contract and authorize the actual operation, not just a preliminary UI action. Choose exception types supported by the real policy client; do not catch every exception and quietly continue. The Python exception tutorial explains propagation and selective handling.
Decide what failure means for the operation¶
For a state-changing database operation, use a transaction or equivalent atomic design so a failed later step does not leave an unintended partial result. For cleanup, use context managers, using, or try-with-resources where appropriate. For retryable work, use bounded retries and an idempotency design; do not blindly repeat an operation that may already have succeeded.
A missing optional cache entry can justify a safe fallback to the authoritative store. A failed permission check cannot justify access. Make the intended behavior explicit in the code and its tests. Public responses and operator logs need separate information policies; see information disclosure and sensitive logging.
Regression test¶
Use a fake policy that explicitly allows and another that denies. Verify both decisions are preserved. Make load_policy and then policy.allows raise OSError containing a fictional secret marker; both must raise AuthorizationUnavailable, produce only the fixed event, and leave the protected-read stub uncalled. Test an unexpected exception too: it must propagate to the controlled request boundary rather than become permission or success.
References: CWE-390 — detection of an error condition without action and CWE-636 — not failing securely.