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.

01in the wild

In the wild

The Empty Catch

Swallowing an exception turns a clear failure into a confusing one, fifty lines away.

example.js
// 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" });
}
An empty catch defers the failure to a later, more confusing crash. Either handle the error meaningfully or let it propagate.
// observed
swallow: undefined access crashes elsewhere
handle:  a clean 400 at the source
example.java
// 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);
}
Catch-all blocks bury real defects and throw away the stack trace. Catch the narrowest exception you can actually recover from.
// observed
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.

example.go
// 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()
Discarding err with _ defers the failure to a nil dereference. Go returns errors as values precisely so you check them at the call site.
// observed
ignored: panic on nil f
checked: the failure is handled where it happens
example.c
/* 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);
In C the only signal is the return value. An unchecked allocation failure becomes silent corruption.
// observed
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.

example.py
# 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 False
When a security check errors, the safe default is to deny. Fail-open turns an outage into an authorization bypass -- the defect becoming a vulnerability.
// observed
fail-open:   verifier down -> everyone is admin
fail-closed: verifier down -> nobody slips through
02weakness catalog

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.