← Back to OWASP Top 10 for Web Applications
A04:20214 min read read

Insecure Design

Build security controls into architecture and design — threat modeling, rate limits, and business-logic safeguards.

URL: trusteed.io/academy/web-security/insecure-design

Insecure Design is a category new to the 2021 OWASP Top 10, and it's distinct from every other category on the list in an important way: it's not about an implementation bug in otherwise sound architecture — it's about missing or inadequate security controls at the architecture and design stage itself. Fixing an Insecure Design flaw often requires re-architecting a feature, not just patching a function, because the vulnerability is the design, not a mistake within it.

What Is Insecure Design?

Every other OWASP Top 10 category describes a way a correct design can be implemented incorrectly — Injection is a design that intended to query a database, implemented with unsafe string concatenation. Insecure Design describes the opposite problem: the design itself never accounted for a threat that should have been considered from the start. A password reset flow that doesn't rate-limit or expire reset tokens is insecurely designed regardless of how cleanly the code implementing it is written. A business flow that allows unlimited free trial signups from the same underlying identity, with no design consideration for abuse, is insecurely designed even if every line of code executes exactly as written. The OWASP API Security Top 10's Unrestricted Access to Sensitive Business Flows is a direct, API-specific instance of this same design-level gap.

Why AI Coding Assistants Get This Wrong

AI assistants implement the feature they're asked to build, as specified. If a request says "build a password reset flow" without specifying rate limiting, token expiration, or abuse resistance, the assistant has no basis for inventing those requirements on its own — it will produce a design that satisfies the literal request, which is often the simplest functional version, not necessarily a threat-modeled one. Insecure Design is fundamentally a requirements-and-review gap, not a coding gap — which means the fix has to happen partly upstream of code generation, in how features are specified in the first place.

Vulnerable Pattern

// Password reset with no rate limiting, no token expiration, and a predictable token
app.post('/api/password-reset/request', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (user) {
    const token = user.id + '-' + Date.now(); // predictable, never expires
    await sendResetEmail(user.email, token);
  }
  res.json({ message: 'If that email exists, a reset link was sent' });
});
// Design gap: nothing prevents thousands of reset requests per minute against any account

Secure Pattern

const crypto = require('crypto');
const rateLimit = require('express-rate-limit');

const resetLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 3, // designed against abuse from the start, not bolted on later
  keyGenerator: (req) => req.body.email, // per-account, not just per-IP
});

app.post('/api/password-reset/request', resetLimiter, async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (user) {
    const token = crypto.randomBytes(32).toString('hex'); // unpredictable
    const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15-minute expiration, designed in
    await ResetToken.create({ userId: user.id, token: hashToken(token), expiresAt });
    await sendResetEmail(user.email, token);
  }
  // Identical response whether or not the email exists — no enumeration signal
  res.json({ message: 'If that email exists, a reset link was sent' });
});

The secure version differs from the vulnerable one not in code quality but in what was considered during design: abuse resistance, token unpredictability, expiration, and enumeration resistance were treated as requirements from the outset, not properties added after a security review flagged their absence.

Copy-Paste Rules for Cursor, Claude Code, and Codex

---
description: Insecure Design prevention — threat modeling and abuse-resistant defaults
alwaysApply: true
---

# Secure Design Rules

- When implementing any sensitive flow (authentication, password reset,
  payment, account creation, referral/coupon redemption), proactively
  identify and implement abuse-resistance requirements — rate limiting,
  token expiration, enumeration resistance — even if not explicitly
  requested, and state what you added and why.
- Treat security-relevant non-functional requirements (expiration windows,
  rate limits, idempotency, unpredictability of tokens/identifiers) as part
  of "done," not as optional hardening to add later.
- For any flow involving monetary value, limited resources, or reputation
  (discounts, inventory, reviews, referrals), consider and flag potential
  abuse vectors during implementation, not only after a report of exploitation.
- Prefer secure-by-default library and framework choices (e.g.,
  cryptographically random tokens by default, ORM query builders by
  default) over manually implemented alternatives that require the
  developer to get every detail right.
- When a request is ambiguous about security-relevant behavior (e.g., "add
  a signup flow" without specifying abuse controls), state the assumption
  being made explicitly rather than silently choosing the least secure
  interpretation.
- Recommend threat modeling for genuinely new, security-sensitive features
  before implementation — surface the question even if it isn't asked.

Verification Checklist

  • Sensitive flows (auth, password reset, payments, account creation) include abuse-resistance controls by design, not as an afterthought
  • Tokens and identifiers used in security-sensitive contexts are cryptographically random, not predictable
  • Time-sensitive credentials (reset tokens, session tokens, OTPs) have explicit, enforced expiration
  • Business flows with monetary or reputational value have been threat-modeled for automation and abuse — see API6:2023 for the API-specific treatment
  • New, security-sensitive features undergo a design/threat-modeling review before implementation begins
  • Continuous testing validates abuse resistance, not just functional correctness — see Trusteed's WAAP platform for behavioral and business-logic testing

Frequently asked questions

Can Insecure Design be caught by automated scanning?

Rarely, and only partially. Automated tools detect known technical patterns; Insecure Design flaws are business-logic and architecture gaps that typically require threat modeling, manual review, or penetration testing to identify — see [Penetration Testing vs. Vulnerability Scanning](https://trusteed.io/academy/glossary/penetration-testing-vs-vulnerability-scanning) for why this category leans heavily on the manual side.

Is threat modeling only necessary for large, complex applications?

No — even a small feature (a password reset flow, a referral program) benefits from a brief threat-modeling pass asking "how could this be abused" before implementation, since the cost of that conversation is minutes, while retrofitting abuse resistance into a shipped feature is substantially more expensive.

Does "secure by default" mean choosing more complex frameworks?

Not necessarily — it means preferring library and framework defaults that already encode good security decisions (parameterized queries by default, auto-escaping templates, cryptographically random ID generation) over manually reimplementing that logic, which introduces more opportunity for a design gap to slip through.