Most of this course deals with implementation bugs: a missing escape, an unparameterized query, a forgotten role check. "Insecure Design" (OWASP A04) is different: the code does exactly what the design says, and the design itself is the vulnerability. No careful coding fixes a password reset that relies on a security question, or a checkout that trusts the browser's price.
In this lesson you will learn to recognise design-level flaws, write abuse cases alongside user stories, and apply the principles that make a design secure before any code exists.
Insecure design shows up as features that work for honest users and catastrophically for dishonest ones:
None of these is a coding mistake. They are decisions made, or never made, before the first line of code.
The cheapest way to catch design flaws is to ask, for every user story, how a hostile user would exploit the feature. Record the answers in the same document:
### Story: As a shopper I can apply a coupon at checkout
Abuse case 1: A shopper applies the same coupon on ten orders.
Control: coupon redemption is atomic and recorded per user; limit is enforced in the database.
Abuse case 2: A shopper edits the cart total in the request.
Control: totals are recomputed server-side from product prices; the client total is ignored.
Abuse case 3: A script tries 100,000 coupon codes.
Control: lookups are rate limited per account; codes are long and random.Each control becomes an acceptance test, as in the threat modeling lesson. The sample code above is the result: prices from the database, quantities bounded, redemption atomic.
| Principle | Meaning | Design question to ask | |---|---|---| | Least privilege | Every component gets only the access it needs | Does this service need write access to that table? | | Defense in depth | No single control is trusted alone | If the WAF fails, what stops the attack? | | Fail securely | Errors deny access rather than grant it | What happens when the permission service times out? | | Secure defaults | The safe option requires no configuration | Is a new bucket private unless someone opens it? | | Separation of duties | Sensitive actions need two parties or two steps | Can one person deploy and approve their own change? | | Never trust the client | Anything from the browser is input | Which fields in this request affect money or privilege? |
Business logic flaws are the hardest to scan for because the code is valid and the request well-formed. Three design rules catch most of them:
// 1. State machines: transitions are explicit, everything else is rejected
const allowed = { pending: ["paid", "cancelled"], paid: ["shipped", "refunded"], shipped: ["delivered"] };
function transition(order, next) {
if (!allowed[order.status]?.includes(next)) throw new Error(`cannot go ${order.status} -> ${next}`);
order.status = next;
}// 2. Idempotency: replays (double-clicks, retries, scripts) have no extra effect
await Payment.create({ key: req.get("Idempotency-Key"), amount }); // unique index on keySecure design is a team habit, not a phase: add a security section to each design document, keep a library of approved patterns, and put "abuse cases reviewed" in the Definition of Done for features that touch money, identity or personal data.
Which of the following is a design flaw rather than an implementation bug?
Next lesson: Security Misconfiguration — the default settings, debug modes and forgotten features that attackers look for first.