← OWASP Top 10:2025

A06:2025

Insecure Design

The code works as designed and the design is the problem: no rate limit on login, a password reset that trusts the email in the request, business logic with no abuse case.

What it is

Every other category is a bug in the implementation. Insecure design is a flaw in the plan. A login with no rate limit is implemented perfectly and still lets someone try a million passwords. A coupon system with no check for reuse works exactly as written. A password reset that emails whichever address is in the request does what it was told. The fix is not a patch; it is a different design.

OWASP added it in 2021 to make the point that shifting left means thinking about abuse before writing code, and it stays at sixth in 2025.

How it shows up in AI-generated code

AI-built apps are unusually prone to this, for a plain reason: the design is the prompt, and the prompt describes the happy path. "Add a login" produces a login. It does not produce rate limiting, lockout, or a check that the reset token is bound to the account, because none of that was in the sentence. The tool builds what was asked, faithfully, and what was asked had no abuse case.

The pattern we see most: rate limiting missing from login, signup, reset and any endpoint that costs money; password resets that accept an email in the body rather than looking it up from the token; file uploads that trust the declared content type; and object IDs that are sequential integers exposed in URLs. None of these are coding errors. They are things nobody asked for.

Example: A password reset that trusts the request

The pattern
// POST /api/reset  { token, email, newPassword }
const record = await db.reset.findUnique({ where: { token } });
if (!record) return fail();
await setPassword(req.body.email, req.body.newPassword);   // email from the client
The fix
const record = await db.reset.findUnique({ where: { token } });
if (!record || record.expiresAt < new Date()) return fail();
await setPassword(record.userId, req.body.newPassword);   // bound to the token
await db.reset.delete({ where: { token } });               // single use
// and: rate limit this route, and the request-reset route, per IP and per account

The token proves who requested the reset; the account must come from the token, never from the request. Add expiry and single use, and rate limit both ends.

How to find it

  • For every endpoint, ask what happens if it is called 10,000 times. If the answer is bad and nothing stops it, that is a missing rate limit.
  • For every flow that changes an account (reset, email change, role change), ask where the identity comes from. If any part comes from the client, that is a design flaw.
  • Walk through the app as an abuser, not a user: reuse a coupon, upload an executable, request someone else's export.
  • Threat model the three features that matter most: money, login, and data export.
  • Prbl catches the implementation traces (missing auth, mass assignment, insecure upload) but design review is a human job; the fix guides linked here are the checklist.

How to fix it

  • Rate limit login, signup, reset, and anything that costs money or sends email, per IP and per account.
  • Bind every sensitive action to the authenticated identity, never to a value in the request.
  • Make tokens expire and be single use.
  • Write the abuse cases into the prompt before generating: "add login with lockout after 10 failures and a rate limit" gets you a different app than "add login".
  • Use non-guessable IDs for anything a user can address by URL.

What Prbl checks for this category

PRBL-A001 Missing access control (implementation trace)Design review checklist below

Run a free scan on a public repo or a live URL. Findings link to the fix guides below.

Fix guides for this category

Go deeper

Common questions

Can a scanner find insecure design?

Partly. Scanners find the implementation traces: a login route with no limiter middleware, an upload with no type check, an update that spreads the whole request body. They cannot know that your coupon should be single use. That part is a design review, which is why this page includes a checklist rather than only rules.

How do I get an AI tool to design securely?

Put the abuse case in the prompt. Tools build what is described. "A login form" and "a login form that locks the account after ten failed attempts, rate limits by IP, and logs failures" produce different code. A rules file with those defaults, applied to every prompt, is the highest-leverage change most teams can make.

What is the difference between insecure design and a missing auth check?

A missing auth check is a slip in code that was supposed to have one. Insecure design is when the plan never included the control at all. In practice both get fixed the same way in an AI-built app: add the control and add it to the rules so the next generation includes it.

Is this category already in something you shipped? Scan a live URL or a public repo free, no account.

Scan my app →
A06:2025 Insecure Design Explained, With Examples From AI-Built Apps | Prbl