Secure design patterns
A secure design pattern is a reusable, proven answer to a recurring security problem in a design, such as one deny-by-default authorization point that every request passes through. Teams reuse the pattern, ideally through a shared component that implements it, instead of designing the control again for each feature.
OWASP Top 10:2025 notes that an insecure design cannot be fixed by a perfect implementation, because the needed control was never created. If each team writes its own authorization checks, the one new endpoint that forgets its check exposes data; behind a deny-by-default choke point, the forgotten check fails closed. One shared pattern also gives reviewers one thing to verify instead of ten variants.
- Who
- Architects and senior developers maintain the patterns; each team applies them, and AppSec reviews departures
- When
- When designing a feature or service, before choosing libraries or writing code
- How often
- On every design; update the catalog when reviews or incidents show a recurring gap
- You end up with
- A short catalog of approved patterns and components, and designs that name the pattern used at each boundary
Do this
Start from a short list of principles
Give designers a checklist: least privilege, fail-safe defaults (deny unless permitted), complete mediation (check every access), economy of mechanism and separation of privilege. Saltzer and Schroeder described these in 1975, and OWASP SAMM's first Secure Architecture activity is a short checklist of principles used during design.
Standardize the security components
Pick one supported option per security function: identity provider and single sign-on, authorization policy, secret store, logging and cryptography library. NIST SSDF PW.1.3 asks software to use standardized security features and services rather than proprietary ones, and PW.4 says reuse matters most for security functions such as cryptographic modules.
Write down a pattern for each boundary type
OWASP Top 10:2025 recommends a library of secure design patterns or paved-road components. Document the pattern for each kind of boundary, with an example: one deny-by-default authorization choke point; the tenant taken from the session, with database row-level security as a backstop; untrusted input parsed into a typed value once at the boundary; idempotency keys and explicit state machines for money and workflows; an egress allowlist for outbound fetches.
Make the safe path the easy path
Ship each pattern as a library, template or framework default: parameterized query builders, auto-escaping templates, secure cookie and CSRF defaults. Give any unsafe escape hatch a loud, searchable name, such as
dangerouslySetInnerHTML,unsafe_orraw, so it stands out in code review.Name the pattern in every design
In the design document or threat model, state the pattern each boundary uses, such as "invoice API: choke-point authorization, tenant-scoped repository". Treat each departure as a decision with a reason and an owner, and send it to the security design review (Security design review).
Test that each pattern holds
Add tests that fail if a pattern is bypassed: a new route with no explicit rule returns 403, a query without the tenant filter returns no rows under row-level security, and an error inside the permission check denies the request. Feed recurring review findings back into the catalog, as OWASP SAMM's Architecture Assessment practice describes.
Done when
The design names the pattern used at each boundary, and any departure is a recorded decision.
Mark it in the gap checkCommon mistakes
- Scattering authorization checks across handlers. The endpoint that forgets its check exposes data, and there is no single place to test.
- Taking the tenant or the owner from the request. Each component passes its own check while the system as a whole serves another tenant's data.
- Failing open. An exception or timeout in a permission or validation step that lets the request through turns an outage into a breach.
- Publishing patterns without components. A pattern on a wiki page gets reimplemented differently by each team; a supported library gets reused.
- Relying on row-level security without checking roles. In PostgreSQL, superusers and roles with
BYPASSRLSalways bypass it, and table owners do unless you setFORCE ROW LEVEL SECURITY. - Assuming layered controls are independent. Two defenses that share one assumption, such as a trusted header, fail together.
Which pattern for which problem?
A short catalog to use as a design checklist. The guide on secure design patterns has examples for each.
| Pattern | Use it when | Prevents |
|---|---|---|
| Choke-point authorization, deny by default | Any system with sensitive actions | Missing or inconsistent access checks |
| Capability token: unguessable, scoped, expiring | Sharing access to one specific object | Tampering with object IDs (IDOR) |
| Parse, don't validate | Any untrusted input | Unchecked values reaching logic |
| Safe-by-default API | Building an API other developers call | Injection and escaping mistakes at each call site |
| Tenant from the session, with row-level security | Multi-tenant data | Cross-tenant data access |
| Fail closed | Any authentication, authorization or validation step | Errors that grant access |
| Least privilege and separation of duties | Components, credentials and dangerous actions | Wide blast radius; one actor completing a high-value action alone |
| Idempotency keys and explicit state machines | Money and multi-step workflows | Double spending and skipped steps |
Tools and guides
- Fix cards: tested fixes by stack
- Secure design patterns: a catalogue for insecure-design (A06)
A catalogue of reusable secure-design patterns, grouped by concern, each with when to use it, a short example and the failure it prevents: choke-point authorization, capability tokens, parse-don't-validate, safe-by-default APIs, tenant scoping, fail-closed, idempotency, and more.
- Authorization models: RBAC, ABAC, ReBAC and policy as code you can test
Translate roles, attributes and relationships into explicit subject-action-resource decisions, prevent broken object level authorization (BOLA), and verify ownership and tenant boundaries with tests.
- Review tenant boundaries before implementation
Trace tenant identity through requests, data stores, caches, jobs, and administration, then turn plausible isolation failures into tests.
- Preserve framework security controls across the request lifecycle
Trace middleware, persistence and rendering so custom code does not bypass the protections supplied by the framework.
Standards
- NIST SSDF 1.1
- PW.1 Design Software to Meet Security Requirements and Mitigate Security RisksPW.4 Reuse Existing, Well-Secured Software When Feasible Instead of Duplicating Functionality
- OWASP SAMM 2
- Secure Architecture
Sources
- Saltzer and Schroeder: The Protection of Information in Computer Systems, basic principles. 1975.
- OWASP Secure Product Design Cheat Sheet
- OWASP SAMM: Secure Architecture, stream A (Architecture Design)
- OWASP SAMM: Architecture Assessment
- NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1. February 2022.
- OWASP Top 10:2025 A06 Insecure Design
- PostgreSQL: row security policies. PostgreSQL 18.