swallowed failures that surface later as something worse
Poor Error Handling
Empty catches, ignored return codes, and fail-open defaults turn a small handled error into a silent failure. The deck's lesson lands here: software that crashes becomes a Denial of Service, and a security check that fails open becomes an authorization bypass.
An error you ignore becomes a crash, a corruption, or a breach.
In the wild
The Empty Catch
Swallowing an exception turns a clear failure into a confusing one, fifty lines away.
// SMELL: the error is swallowed; the program limps on corrupt
let user;
try {
user = JSON.parse(body);
} catch (e) { /* ignore */ } // user is undefined, used later
// RIGHT: handle it, or fail loudly -- never swallow
try {
user = JSON.parse(body);
} catch (e) {
return res.status(400).json({ error: "invalid body" });
}swallow: undefined access crashes elsewhere handle: a clean 400 at the source
// SMELL: catch (Exception) hides bugs you never meant to catch
try {
transfer(amount);
} catch (Exception e) { // also catches NPEs and logic bugs
log.info("retrying"); // and discards the stack trace
}
// RIGHT: catch the specific, recoverable exception; keep the cause
try {
transfer(amount);
} catch (InsufficientFundsException e) {
notifyUser(e);
}broad: a logic bug looks like a retry narrow: only the expected failure is handled
Ignored Return Codes
Languages that return errors as values let you discard them -- and the discard is a decision to be surprised later.
// SMELL: the error is discarded with _
f, _ := os.Open(path) // f may be nil
defer f.Close() // nil pointer dereference
// RIGHT: check every error the moment it returns
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()ignored: panic on nil f checked: the failure is handled where it happens
/* SMELL: malloc can return NULL; the unchecked write corrupts memory */
char *buf = malloc(n);
memcpy(buf, src, n); /* segfault or worse if buf == NULL */
/* RIGHT: check the return code before you trust it */
char *buf = malloc(n);
if (!buf) return -1;
memcpy(buf, src, n);unchecked: undefined behavior on OOM checked: a clean error path
Fail-Open vs Fail-Closed
When a security check errors, the safe default is to deny. Fail-open turns an outage into a bypass.
# SMELL: on error, default to allowing the request (fail OPEN)
def is_authorized(token):
try:
return verify(token)
except Exception:
return True # a verifier outage grants everyone access
# RIGHT: on error, deny (fail CLOSED)
def is_authorized(token):
try:
return verify(token)
except Exception:
return Falsefail-open: verifier down -> everyone is admin fail-closed: verifier down -> nobody slips through
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.