Insecure Design and Secure-by-Design Principles

Intermediate
10 min

Insecure Design and Secure-by-Design Principles

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.

What Makes a Design Insecure

Insecure design shows up as features that work for honest users and catastrophically for dishonest ones:

  • A coupon that can be applied twice because "redeem" and "apply" are separate requests.
  • A price, discount or quantity accepted from the client and stored as sent.
  • Account recovery through questions whose answers are on social media.
  • A multi-tenant database with no tenant column, so isolation depends on every query remembering a filter.
  • Unlimited attempts on anything: login, OTP entry, referral codes.

None of these is a coding mistake. They are decisions made, or never made, before the first line of code.

Write Abuse Cases Next to User Stories

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:

markdown
### 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.

Secure Design Principles

| 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? |

Designing Business Logic Defensively

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:

javascript
// 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; }
javascript
// 2. Idempotency: replays (double-clicks, retries, scripts) have no extra effect await Payment.create({ key: req.get("Idempotency-Key"), amount }); // unique index on key
  1. Limits everywhere. Maximum quantities, attempts, file counts and page sizes. A missing bound is an invitation.

Secure 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.

Quick Quiz
Question 1 of 2

Which of the following is a design flaw rather than an implementation bug?

Key Takeaways

  • Insecure design is a flaw in what the system is meant to do; correct code cannot fix it.
  • Write an abuse case for every user story that touches money, identity or data; each control becomes a test.
  • Apply least privilege, defense in depth, fail securely, secure defaults and never trust the client.
  • Model business logic as explicit state machines with idempotent operations and hard limits.
  • Make secure design part of the process: design docs, approved patterns, Definition of Done.

Next lesson: Security Misconfiguration — the default settings, debug modes and forgotten features that attackers look for first.

Insecure Design and Secure-by-Design Principles - Cyber Security | CodeYourCraft | CodeYourCraft