The Computing Series

Introduction

The bug was in production for eleven months before anyone found it.

A payments platform had an authorization check:

if not user.is_suspended or user.has_override:
    process_payment(user, amount)

The intent was: “process if the user is not suspended, OR if they have a manual override.” The code reads like that intent. But it does not implement it.

The correct logic is: “process if (not suspended) OR (has override).” The code reads not user.is_suspended or user.has_override, which, by operator precedence, evaluates as (not user.is_suspended) or (user.has_override).

Those are the same thing. So where is the bug?

The bug was in a different function — one that computed is_suspended by combining two flags:

# Original intent: suspended if account_frozen AND payment_blocked
user.is_suspended = account_frozen and payment_blocked

A developer needed to change the policy: “suspended if account_frozen OR payment_blocked.” They updated the assignment:

user.is_suspended = account_frozen or payment_blocked

Then the authorization check, which negated is_suspended, silently changed meaning. The original check read: “not (frozen AND blocked).” By De Morgan’s Law, this equals “(not frozen) OR (not blocked)” — which is True for almost every user. The intent was never this permissive. Accounts that were frozen but not blocked could process payments.

Eleven months. The fix was one line. The damage was regulatory.

De Morgan’s Laws are not an academic curiosity. They are the source of real authorization failures.


Read in the book →