Latest Intelligence

πŸ”“ OWASP Top 10 β€” A04:2021 Insecure Design

8/1/2026
Research Report

πŸ”“ OWASP Top 10 β€” A04:2021 Insecure Design

When Your Architecture Is the Vulnerability: A Deep Dive Into Everything That Goes Wrong Before a Single Line of Code Is Written

"The most expensive bug is the one born in the whiteboard session, not in the code editor."

>

This blog dissects A04:2021 β€” Insecure Design down to the atomic level. By the time you finish reading, you will never again say "we'll add security later."

πŸ“– Table of Contents

  1. Introduction β€” Why Does This Category Exist?
  2. What Exactly Is Insecure Design?
  3. Insecure Design vs Insecure Implementation
  4. Real-World Attack Scenarios
  5. Technical Deep-Dive: CWE Mapping
  6. Threat Modeling β€” Your First Line of Defense
  7. Secure Design Patterns
  8. Anti-Patterns: What NOT to Do
  9. Defense-in-Depth Strategy
  10. Secure SDLC Integration
  11. Code Examples: Vulnerable vs Secure
  12. Tools & Resources
  13. The Ultimate Checklist
  14. Conclusion
  15. πŸšͺ Introduction β€” Why Does This Category Exist?

When OWASP refreshed its Top 10 list in 2021, it introduced a brand-new category: A04:2021 β€” Insecure Design. This was not an accident. It was an acknowledgment of a painful truth that the security community had been screaming about for years:

πŸ”΄ The majority of catastrophic vulnerabilities are not born in code β€” they are born in architecture.

Think of it this way: if you lay the foundation of a house crooked, no amount of beautiful paint on the walls will stop that house from collapsing. Software is exactly the same.

The Numbers Tell the Story

MetricValue
Incidence Rate (tested applications)3.00%
Mapped CWEs40
Max Incidence Rate24.19%
Mapped CVEs262,407
Average Weighted CVSS Score6.46

These numbers reveal something critical β€” insecure design is endemic. It is not a rare edge case. It is the norm in most organizations that do not deliberately invest in secure architecture.

The Cost of Fixing Bugs by Phase

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  RELATIVE COST TO FIX                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Requirements  β”‚ πŸ’° x1                                    β”‚
β”‚ Design        β”‚ πŸ’°πŸ’° x5                                  β”‚
β”‚ Development   β”‚ πŸ’°πŸ’°πŸ’° x10                               β”‚
β”‚ Testing       β”‚ πŸ’°πŸ’°πŸ’°πŸ’° x20                             β”‚
β”‚ Production    β”‚ πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’° x100              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Source: IBM Systems Sciences Institute / NIST

A design-level flaw found in production can cost 100x more to fix than if it had been caught during the requirements phase. That is not a typo. One hundred times.

  1. 🧠 What Exactly Is Insecure Design?

Insecure Design is a broad category representing different weaknesses expressed as missing or ineffective control design. It refers to flaws in the fundamental architecture and design decisions of a system β€” decisions made (or not made) before any code is written.

In Plain English

text
Insecure Design = "We didn't think about that"
                 + "Who would even do that?"
                 + "We'll add security later"
                 + "That's an edge case, nobody will try it"

The Official OWASP Definition

"An insecure design cannot be fixed by a perfect implementation as by definition, needed security controls were never created to defend against specific attacks."

Read that last part again: "needed security controls were never created." The defense mechanism was never designed to exist. You cannot patch something that was never built.

The Building Analogy

ScenarioCategoryFix
🏦 You designed a bank with a vault, cameras, laser sensors, and armed guards β€” but the lock on the front door turned out to be defectiveImplementation BugReplace the lock. Done.
🏚️ You designed a bank with no vault, no cameras, no sensors, and a permanently open back doorInsecure DesignTear the building down and redesign it from scratch.

The second scenario is exponentially more expensive and fundamentally more dangerous.

What Insecure Design Is NOT

Let's be crystal clear about the boundaries:

text
❌ Insecure Design is NOT:
   - A specific vulnerability like SQL Injection or XSS
   - A coding mistake
   - A misconfiguration
   - Something that can be caught by a SAST scanner

βœ… Insecure Design IS:
   - A missing security control that was never planned
   - A flawed business logic that enables abuse
   - An architecture that trusts untrusted input by design
   - A system that fails open instead of failing secure
   - The absence of rate limiting, MFA, or abuse prevention
     because nobody thought to include them
  1. βš–οΈ Insecure Design vs Insecure Implementation β€” The Critical Distinction

These two concepts are confused constantly, even by experienced developers. Let's eliminate all ambiguity.

Side-by-Side Comparison

CharacteristicInsecure DesignInsecure Implementation
When does it happen?Architecture & design phaseCoding & configuration phase
Root CauseSecurity requirements missing or inadequateSecurity requirements exist but are incorrectly coded
How to fixRedesign the architecturePatch the code
How to detectThreat modeling, design review, abuse case analysisCode review, SAST, DAST, penetration testing
Cost to fixπŸ’°πŸ’°πŸ’°πŸ’°πŸ’° (Extremely expensive)πŸ’°πŸ’° (Relatively affordable)
ExampleNo password recovery mechanism existsPassword recovery uses MD5 hash
Can perfect code fix it?❌ NEVERβœ… Yes

The Golden Rule

text
βœ… Perfect Implementation + Weak Design   = VULNERABLE SYSTEM (always)
🟑 Weak Implementation   + Strong Design  = FIXABLE SYSTEM (patch it)
βœ… Perfect Implementation + Strong Design  = SECURE SYSTEM (the goal)

This is the single most important takeaway: you cannot code your way out of a design flaw. No matter how clean, elegant, or well-tested your code is β€” if the architecture is fundamentally insecure, the system is compromised.

  1. πŸ’€ Real-World Attack Scenarios

Let's walk through detailed, realistic scenarios that demonstrate how insecure design manifests in production systems.

πŸ“Œ Scenario 1: Cinema Booking System β€” The Scalper Attack

Why is this Insecure Design and NOT an implementation bug?

The code works exactly as designed. The problem is that the design itself is flawed:

  • ❌ Mass reservation without payment was an architectural decision
  • ❌ No rate limiting was designed into the API
  • ❌ No bot detection was planned in the system architecture
  • ❌ No per-user/per-IP seat limit was specified in requirements
  • ❌ No abuse case analysis was performed during design

The Secure Design Alternative:

text
βœ… Maximum 4 seats per session/user
βœ… Reservation window: 5 minutes (not 15)
βœ… No seat lock until payment process begins ("soft hold" only)
βœ… CAPTCHA + device fingerprinting for all reservations
βœ… Behavioral analysis: flag accounts making rapid bulk reservations
βœ… Queue system for high-demand events
βœ… Proof-of-work challenge for API consumers

πŸ“Œ Scenario 2: E-Commerce β€” Price Manipulation

text
πŸ”΄ INSECURE DESIGN:

Client sends:
POST /api/order
{
    "product_id": 1337,
    "quantity": 1,
    "price": 0.01,        ← The CLIENT dictates the price!
    "discount": 99,        ← The CLIENT dictates the discount!
    "currency": "USD"
}

Server blindly trusts and processes the order at $0.01

The developer didn't make a "coding mistake." The architecture decided that the client would send pricing information, and the server would trust it. This is a design decision.

text
βœ… SECURE DESIGN:

Client sends:
POST /api/order
{
    "product_id": 1337,
    "quantity": 1
    "text-slate-500 font-normal italic">// Price is NEVER accepted from the client
    "text-slate-500 font-normal italic">// Discount codes are validated server-side
}

Server-side logic:
1. Fetch product price from database
2. Validate discount code against server-side rules
3. Calculate total on the server
4. Verify calculated total matches expected range
5. Write audit log for every transaction
6. Flag transactions with unusual discount patterns

πŸ“Œ Scenario 3: Password Recovery β€” Knowledge-Based Authentication

Why is this a design flaw?

  • Security questions are static β€” your mother's maiden name never changes
  • Answers are often publicly available on social media
  • They can be brute-forced (limited answer space)
  • The entire recovery mechanism is architecturally weak

The Secure Design:

text
βœ… Email-based recovery link with 15-minute expiry
βœ… SMS/TOTP OTP with rate limiting (max 3 attempts)
βœ… MFA challenge before allowing password reset
βœ… Pre-generated account recovery codes (printed and stored safely)
βœ… Identity verification through multiple independent factors
βœ… Notify the user on ALL registered channels when a reset is requested

πŸ“Œ Scenario 4: Credential Stuffing β€” Defenseless by Design

The server code may be perfectly written β€” parameterized queries, proper password hashing, clean error handling. But if the system was never designed to handle automated large-scale login attempts, it is insecure by design.

πŸ“Œ Scenario 5: IDOR β€” A Design-Level Perspective

text
πŸ”΄ INSECURE DESIGN:

GET /api/users/1001/medical-records    ← User 1001's medical data

Attacker simply changes the ID:
GET /api/users/1002/medical-records    ← Someone else's medical data! πŸ’€

This is an Insecure Design problem because:

  1. No authorization layer was designed β€” the principle "each user can only access their own data" was never embedded into the architecture
  2. Sequential integer IDs were chosen as the identifier scheme (a design decision) β€” making enumeration trivial
  3. No Access Control Matrix was ever drawn during the design phase
  4. No resource-level authorization policy was specified in the requirements
text
βœ… SECURE DESIGN:

1. Use UUIDs: GET /api/users/a8f3b2c1-4d5e.../medical-records
   β†’ Eliminates enumeration

2. Resource-level authorization on EVERY request:
   β†’ Token's user_id == requested resource's owner_id?
   β†’ Does the user have the required role/permission?

3. Consistent authorization enforcement:
   β†’ Centralized policy engine (OPA, Casbin, etc.)
   β†’ Every endpoint goes through the same authz middleware

4. Return 404 (not 403) for unauthorized access:
   β†’ Don't reveal that the resource exists

5. Comprehensive audit logging:
   β†’ Log every access attempt (success AND failure)

πŸ“Œ Scenario 6: E-Commerce β€” Coupon Abuse (Business Logic Flaw)

text
πŸ”΄ INSECURE DESIGN β€” No abuse case was ever considered:

1. User applies coupon code "WELCOME50" (-50% discount)
2. User places order β†’ pays $50 instead of $100
3. User cancels the order β†’ gets full $50 refund
4. User applies the SAME coupon again β†’ pays $50
5. Repeat infinitely

Worse:
6. User discovers coupon codes are sequential: WELCOME50, WELCOME51, WELCOME52...
7. User brute-forces valid coupon codes
8. User shares valid codes on public forums

βœ… SECURE DESIGN:

1. Coupon usage tracked per-user in database (one-time use flag)
2. Coupon bound to specific user segment (new users only, etc.)
3. Refund does NOT automatically re-enable coupon β€” manual review required
4. Coupon codes use cryptographically random strings (not sequential)
5. Anomaly detection: flag accounts with excessive coupon usage
6. Coupon issuance rate limiting
7. Server-side validation of ALL coupon rules on every application
  1. πŸ—ΊοΈ Technical Deep-Dive: CWE Mapping

A04:2021 maps to 40 CWEs. Here are the most critical ones, organized by theme:

Detailed CWE Breakdown

πŸ”Έ CWE-209: Generation of Error Message Containing Sensitive Information

python
# πŸ”΄ VULNERABLE β€” Stack trace and internal details exposed to user
@app.route('/login', methods=['POST'])
def login():
    try:
        user = db.execute(
            f"SELECT * FROM users WHERE email='{request.json['email']}'"
        )
        if not verify_password(request.json['password'], user.password_hash):
            return {
                "error": f"Wrong password for user {request.json['email']} "
                         f"in table 'users' (schema: public, db: prod_userdb)"
            }, 401  # ← Leaks table name, schema, database name
    except Exception as e:
        return {"error": str(e)}, 500
        # ← Leaks full stack trace, SQL errors, internal paths


# βœ… SECURE β€” Generic messages to user, detailed logs internally
@app.route('/login', methods=['POST'])
def login():
    try:
        user = authenticate(request.json.get('email'), request.json.get('password'))
        if not user:
            return {"error": "Invalid email or password"}, 401
            # ← Same message whether email exists or not (prevents enumeration)
    except Exception as e:
        error_id = str(uuid.uuid4())
        logger.error(f"Login error [{error_id}]: {e}", exc_info=True)
        # ← Full details go to internal logs only
        return {
            "error": "An unexpected error occurred",
            "reference": error_id
            # ← User gets a reference ID to report, not the actual error
        }, 500

πŸ”Έ CWE-602: Client-Side Enforcement of Server-Side Security

javascript
// πŸ”΄ VULNERABLE β€” Authorization enforced ONLY in JavaScript
function deleteUser(userId) {
    if (currentUser.role === 'admin') {  // ← Client-side check only
        fetch(`/api/users/${userId}`, { method: 'DELETE' });
    } else {
        alert('Access denied!');
    }
}

// Attacker opens DevTools, runs:
// fetch('/api/users/1002', { method: 'DELETE' })
// β†’ Server happily deletes the user because it has NO authorization check
python
# βœ… SECURE β€” Authorization enforced on the SERVER
@app.route('/api/users/<user_id>', methods=['DELETE'])
@require_auth  # ← Authentication middleware
def delete_user(user_id):
    # Server-side authorization check
    if not current_user.has_permission('user:delete'):
        audit_log.warning(
            f"Unauthorized delete attempt by user {current_user.id} "
            f"for target {user_id}"
        )
        abort(403)  # ← Server says NO
    
    # Additional check: prevent self-deletion
    if user_id == current_user.id:
        abort(400, "Cannot delete your own account")
    
    # Proceed with deletion
    user = User.query.get_or_404(user_id)
    user.soft_delete()  # ← Soft delete, not hard delete (another design decision)
    db.session.commit()
    
    audit_log.info(f"User {user_id} deleted by admin {current_user.id}")
    return {"message": "User deleted"}, 200

πŸ”Έ CWE-799: Improper Control of Interaction Frequency

text
Design Flaw: No rate limiting on OTP verification endpoint

πŸ”΄ VULNERABLE:
POST /api/verify-otp
{ "phone": "+1234567890", "otp": "1234" }

β†’ 4-digit OTP = only 10,000 possible combinations
β†’ No rate limiting = attacker tries all 10,000 in ~30 seconds
β†’ Account takeover guaranteed

βœ… SECURE DESIGN:
1. 6-digit OTP (1,000,000 combinations)
2. Max 3 attempts per OTP
3. OTP expires after 5 minutes
4. 30-second cooldown between OTP requests
5. Account lockout after 5 failed OTPs in 1 hour
6. CAPTCHA after 2 failed attempts
7. Notify user of failed verification attempts

πŸ”Έ CWE-840: Business Logic Errors

  1. 🎯 Threat Modeling β€” Your First Line of Defense

Threat Modeling is the most powerful antidote to insecure design. It forces you to think like an attacker before any code is written.

The STRIDE Model

The Threat Modeling Process β€” Step by Step

Data Flow Diagram (DFD) β€” E-Commerce Example

πŸ”‘ Key Principle: Every time data crosses a trust boundary, it MUST be validated, sanitized, and authorized. This is a design decision, not an implementation detail.

DREAD Risk Scoring Table

ThreatDamageReproducibilityExploitabilityAffected UsersDiscoverabilityScore
Credential Stuffing (no rate limit)8109988.8 πŸ”΄
Price Manipulation (client-side pricing)9107667.6 🟠
IDOR on Medical Records1098757.8 πŸ”΄
Error Message Information Leak31010296.8 🟑
Coupon Abuse (business logic)7108477.2 🟠
Unlimited File Upload (no size/type check)897867.6 🟠

Scoring Guide: (1 = Low, 10 = High)

  • πŸ”΄ 7.0+ = Critical β€” Fix immediately during design
  • 🟠 5.0–6.9 = High β€” Must address before development begins
  • 🟑 3.0–4.9 = Medium β€” Plan mitigation
  • 🟒 < 3.0 = Low β€” Accept or monitor
  1. πŸ—οΈ Secure Design Patterns

Pattern 1: Defense in Depth

Never rely on a single security control. Layer your defenses so that if one layer fails, others still protect the system.

Pattern 2: Zero Trust Architecture

text
πŸ” Principle: "Never trust, always verify"

For EVERY request, regardless of origin:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  1. Verify Identity    β€” WHO is making this request? β”‚
β”‚  2. Verify Device      β€” WHAT device are they using? β”‚
β”‚  3. Verify Context     β€” WHERE, WHEN, and WHY?       β”‚
β”‚  4. Enforce Least Privilege β€” Minimum permissions     β”‚
β”‚  5. Assume Breach      β€” Monitor and log everything   β”‚
β”‚  6. Verify Explicitly  β€” Don't trust cached authz     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Traditional Security:  "Inside the firewall = trusted"      ❌
Zero Trust:            "Nothing is trusted, ever, period"    βœ…

Pattern 3: Fail Secure (Not Fail Open)

python
# ═══════════════════════════════════════════════════
# πŸ”΄ FAIL OPEN β€” If auth service is down, allow everything
# ═══════════════════════════════════════════════════

def check_permission(user, resource):
    try:
        return authorization_service.check(user, resource)
    except ServiceUnavailableError:
        return True  # ← If auth service is down, GRANT ACCESS TO EVERYTHING 😱
        # "We can't let auth service downtime affect user experience!"


# ═══════════════════════════════════════════════════
# βœ… FAIL SECURE β€” If auth service is down, deny everything
# ═══════════════════════════════════════════════════

def check_permission(user, resource):
    try:
        return authorization_service.check(user, resource)
    except ServiceUnavailableError:
        logger.critical(
            "Authorization service is DOWN! "
            "All requests are being denied as a security precaution."
        )
        alert_ops_team(severity="P1")
        return False  # ← If auth service is down, DENY ALL ACCESS πŸ”’
        # "Security is more important than availability in this context"
    except Exception as e:
        logger.error(f"Unexpected authorization error: {e}")
        return False  # ← Default deny for ANY unexpected error

Pattern 4: Principle of Least Privilege (PoLP)

Pattern 5: Secure Defaults

yaml
# βœ… Everything should be CLOSED/RESTRICTED by default
# Access is GRANTED explicitly, never implicitly

security:
  cors:
    allowed_origins: []              # Default: NO origins allowed
    allowed_methods: ["GET"]         # Default: read-only
  authentication:
    required: true                   # Default: auth REQUIRED
    mfa_enabled: true                # Default: MFA ON
    password_min_length: 12          # Default: 12 characters
    password_require_complexity: true
  session:
    timeout_minutes: 15              # Default: 15-minute timeout
    secure_cookie: true              # Default: HTTPS only
    same_site: "Strict"              # Default: strict SameSite
    http_only: true                  # Default: no JS access to cookies
  rate_limiting:
    enabled: true                    # Default: rate limiting ON
    requests_per_minute: 60          # Default: 60 req/min
    login_attempts_per_hour: 10      # Default: 10 login attempts/hour
  file_upload:
    enabled: false                   # Default: uploads DISABLED
    max_size_mb: 5                   # Default: 5MB max
    allowed_types: []                # Default: NO file types allowed
  logging:
    audit_trail: true                # Default: audit ON
    log_auth_events: true            # Default: log all auth events
  headers:
    x_content_type_options: "nosniff"
    x_frame_options: "DENY"
    strict_transport_security: "max-age=31536000; includeSubDomains"
    content_security_policy: "default-src 'self'"

Pattern 6: Separation of Duties

text
πŸ” Critical operations should require MULTIPLE actors:

Example: Financial Transfer > $10,000

Step 1: Initiator (Finance Analyst) creates the transfer request
Step 2: Reviewer (Finance Manager) approves the transfer
Step 3: System verifies both actors are different people
Step 4: System verifies both actors have the required roles
Step 5: Transfer is executed
Step 6: Both actors are notified of completion
Step 7: Audit trail records all actors, timestamps, and actions

❌ One person should NEVER be able to:
   - Create AND approve a transaction
   - Write code AND deploy it to production
   - Create a user AND assign admin privileges
  1. 🚫 Anti-Patterns: What NOT to Do

Anti-Pattern 1: Security by Obscurity

text
πŸ”΄ "Nobody knows our API endpoint, so we don't need authentication"
πŸ”΄ "This port isn't commonly scanned, so we don't need to secure it"
πŸ”΄ "Our source code is proprietary, so nobody can find the vulnerabilities"
πŸ”΄ "We use a custom encryption algorithm that nobody knows about"

βœ… Kerckhoffs' Principle (1883):
   "A cryptographic system should be secure even if everything about 
    the system, except the key, is public knowledge."

   Applied broadly: Your system should be secure even if the attacker 
   has your source code, knows your architecture, and understands 
   every design decision you made.

Anti-Pattern 2: Trusting the Client

text
πŸ”΄ Storing price in a hidden HTML field
πŸ”΄ Performing authorization checks in JavaScript only
πŸ”΄ Relying on client-side input validation without server-side validation
πŸ”΄ Using a "disabled" button as a security control (DevTools β†’ enable β†’ click)
πŸ”΄ Sending user role in a cookie or local storage and trusting it
πŸ”΄ Using client-side feature flags for security-sensitive features

βœ… THE GOLDEN RULE:
   "All input from the client is HOSTILE until proven otherwise."
   
   The client (browser, mobile app, API consumer) is ALWAYS under
   the attacker's control. EVERY input, header, cookie, and parameter
   must be validated, sanitized, and authorized on the SERVER.

Anti-Pattern 3: "Happy Path Only" Design

Secure design requires thinking about:

  • 😈 Abuse cases β€” How will attackers misuse this feature?
  • πŸ’₯ Failure modes β€” What happens when things go wrong?
  • πŸ”„ Edge cases β€” What happens with boundary values?
  • ⚑ Race conditions β€” What happens with concurrent requests?
  • πŸ“Š Scale attacks β€” What happens with 1 million requests?

Anti-Pattern 4: Monolithic Authorization

text
πŸ”΄ WRONG: A single "isAdmin" boolean controls everything

if (user.isAdmin) {
    "text-slate-500 font-normal italic">// Can do LITERALLY ANYTHING:
    "text-slate-500 font-normal italic">// - Delete all users
    "text-slate-500 font-normal italic">// - Access financial records
    "text-slate-500 font-normal italic">// - Modify system configuration
    "text-slate-500 font-normal italic">// - Export all data
    "text-slate-500 font-normal italic">// - Impersonate other users
}

βœ… CORRECT: Granular, resource-level permission system

Permission Matrix:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Resource             β”‚ Read β”‚ Write β”‚ Delete β”‚ Export   β”‚ Admin     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Own Profile          β”‚  βœ…  β”‚  βœ…   β”‚  ❌   β”‚  βœ…      β”‚  ❌      β”‚
β”‚ Team Members         β”‚  βœ…  β”‚  ❌   β”‚  ❌   β”‚  ❌      β”‚  ❌      β”‚
β”‚ Department Data      β”‚  βœ…  β”‚  βœ…   β”‚  ❌   β”‚  ❌      β”‚  ❌      β”‚
β”‚ Financial Reports    β”‚  ❌  β”‚  ❌   β”‚  ❌   β”‚  ❌      β”‚  ❌      β”‚
β”‚ System Configuration β”‚  ❌  β”‚  ❌   β”‚  ❌   β”‚  ❌      β”‚  ❌      β”‚
β”‚ Audit Logs           β”‚  ❌  β”‚  ❌   β”‚  ❌   β”‚  ❌      β”‚  ❌      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Anti-Pattern 5: "We'll Add Security Later"

text
The Real Timeline:

Sprint 1:  "Let's build the feature first, we'll add auth later"
Sprint 2:  "We're behind schedule, security will have to wait"
Sprint 3:  "The feature works, let's ship it"
Sprint 4:  "We have new features to build, security can wait"
Sprint 5:  "We have too much tech debt to refactor for security now"
Sprint 47: "We got breached"
Sprint 48: "Why didn't we add security earlier?"

Cost to add security in Sprint 1:    πŸ’°
Cost to add security in Sprint 48:   πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°
  1. πŸ›‘οΈ Defense-in-Depth Strategy

Complete Defense Architecture

Why Each Layer Matters

text
If Layer 0 fails: β†’ Attacker bypasses DNS/CDN protections
   But Layer 1 (WAF) blocks malicious requests βœ…

If Layer 1 fails: β†’ Malicious traffic reaches your network
   But Layer 2 (segmentation) limits blast radius βœ…

If Layer 2 fails: β†’ Attacker moves laterally in the network
   But Layer 3 (identity) requires strong authentication βœ…

If Layer 3 fails: β†’ Attacker authenticates somehow
   But Layer 4 (application) validates all input and enforces authz βœ…

If Layer 4 fails: β†’ Attacker exploits an application vulnerability
   But Layer 5 (data) keeps data encrypted and tokenized βœ…

If Layer 5 fails: β†’ Attacker accesses raw data
   But Layer 6 (monitoring) detects the breach in real-time βœ…

Every layer must fail INDEPENDENTLY for a full compromise.
  1. πŸ”„ Secure SDLC Integration

Secure Design is not a one-time activity β€” it must be woven into every phase of the Software Development Lifecycle.

Phase-by-Phase Activities

πŸ“‹ Phase 1: Requirements

text
Security Requirements Gathering:

β–‘ Functional security requirements defined?
  β†’ "Users MUST authenticate with MFA before accessing financial data"
  
β–‘ Abuse cases / misuse cases written?
  β†’ "An attacker WILL attempt to brute-force the login endpoint"
  β†’ "A malicious user WILL try to access other users' data"
  β†’ "An insider WILL attempt to exfiltrate customer data"
  
β–‘ Compliance requirements mapped?
  β†’ GDPR (data privacy), PCI-DSS (payment), HIPAA (health), SOC 2
  
β–‘ Data classification completed?
  β†’ Public | Internal | Confidential | Restricted
  
β–‘ Security SLAs defined?
  β†’ "Critical vulnerabilities must be patched within 24 hours"
  β†’ "99.9% uptime with DDoS protection"
  
β–‘ Privacy requirements specified?
  β†’ Data retention periods, right to deletion, consent management

πŸ“ Phase 2: Design

text
Secure Design Activities:

β–‘ Threat Modeling conducted? (STRIDE / PASTA / LINDDUN)
β–‘ Attack surface analysis performed?
β–‘ Security architecture review completed by security team?
β–‘ Trust boundaries clearly identified and documented?
β–‘ Secure design patterns applied?
β–‘ Failure modes analyzed? (Fail secure, not fail open)
β–‘ Authentication & authorization architecture designed?
β–‘ Data flow diagrams created with security annotations?
β–‘ Third-party integration risks assessed?
β–‘ Cryptographic choices documented and justified?

πŸ’» Phase 3: Development

text
Secure Development Practices:

β–‘ Secure coding standards adopted and enforced?
β–‘ SAST tools integrated into CI/CD pipeline?
β–‘ Pre-commit hooks for secret detection? (git-secrets, detect-secrets)
β–‘ Dependency scanning active? (Snyk, Dependabot)
β–‘ Security-focused code reviews conducted?
β–‘ Secret management solved? (HashiCorp Vault, AWS Secrets Manager)
β–‘ Parameterized queries / ORM used? (No string concatenation for SQL)
β–‘ Output encoding applied in all templates?
β–‘ Security unit tests written for auth/authz logic?

πŸ§ͺ Phase 4: Testing

text
Security Testing Activities:

β–‘ DAST scan completed?
β–‘ Penetration testing performed by qualified testers?
β–‘ Business logic testing done? (abuse cases from Phase 1)
β–‘ Authentication bypass testing?
β–‘ Authorization bypass testing? (horizontal & vertical)
β–‘ Race condition testing? (TOCTOU flaws)
β–‘ Fuzz testing for input parsing?
β–‘ API security testing? (OWASP API Security Top 10)
β–‘ Session management testing?
β–‘ Error handling and information leakage testing?
  1. πŸ’» Code Examples: Vulnerable vs Secure

Example 1: User Registration β€” Multi-Layered Defense

python
# ══════════════════════════════════════════════════════════════
# πŸ”΄ INSECURE DESIGN β€” No rate limiting, no validation, no defense
# ══════════════════════════════════════════════════════════════

@app.route('/api/register', methods=['POST'])
def register():
    email = request.json.get('email')
    password = request.json.get('password')
    
    # No rate limiting β€” a bot can create 100,000 accounts per hour
    # No CAPTCHA β€” automated registration is trivial
    # No email verification β€” fake emails accepted
    # No password policy β€” "1" is a valid password
    # No duplicate check timing safety β€” email enumeration possible
    
    user = User(email=email, password=hash_password(password))
    db.session.add(user)
    db.session.commit()
    
    return {"message": "Registered successfully"}, 201


# ══════════════════════════════════════════════════════════════
# βœ… SECURE DESIGN β€” Defense in depth at every step
# ══════════════════════════════════════════════════════════════

from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import re
import uuid
import bcrypt
import secrets
from datetime import datetime, timedelta

limiter = Limiter(app, key_func=get_remote_address)

# Design Decisions documented:
# 1. IP-based rate limiting (5 registrations per hour per IP)
# 2. CAPTCHA verification before processing
# 3. Strict email format validation (server-side)
# 4. Strong password policy (12+ chars, complexity, breach check)
# 5. Timing-safe duplicate check (prevent email enumeration)
# 6. Email verification required before account activation
# 7. Comprehensive audit logging
# 8. No sensitive data in responses

@app.route('/api/register', methods=['POST'])
@limiter.limit("5 per hour")  # Design: max 5 registrations per IP per hour
def register():
    data = request.json or {}
    
    # ── Step 1: CAPTCHA Verification ──
    captcha_token = data.get('captcha_token')
    if not captcha_token or not verify_captcha(captcha_token):
        audit_log.warning(f"Failed CAPTCHA from {request.remote_addr}")
        return {"error": "CAPTCHA verification failed"}, 400
    
    # ── Step 2: Input Validation (Server-Side, Whitelist) ──
    email = data.get('email', '').strip().lower()
    password = data.get('password', '')
    
    if not email or not re.match(
        r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email
    ):
        return {"error": "Invalid email format"}, 400
    
    if len(email) > 254:  # RFC 5321 max email length
        return {"error": "Email address too long"}, 400
    
    # ── Step 3: Password Policy Enforcement ──
    password_errors = validate_password_strength(password)
    if password_errors:
        return {"error": "Password does not meet requirements",
                "details": password_errors}, 400
    
    # ── Step 4: Breached Password Check (HaveIBeenPwned API) ──
    if is_password_breached(password):
        return {
            "error": "This password has been found in known data breaches. "
                     "Please choose a different password."
        }, 400
    
    # ── Step 5: Timing-Safe Duplicate Check ──
    # Always take the same amount of time regardless of whether
    # the email exists β€” prevents email enumeration attacks
    processing_start = datetime.utcnow()
    
    existing_user = User.query.filter_by(email=email).first()
    
    # Ensure consistent response time (200-300ms)
    elapsed = (datetime.utcnow() - processing_start).total_seconds()
    if elapsed < 0.25:
        time.sleep(0.25 - elapsed)
    
    if existing_user:
        # SAME response as success β€” attacker can't tell if email exists
        return {
            "message": "If this email is not already registered, "
                       "you will receive a verification email shortly."
        }, 200
    
    # ── Step 6: Create Unverified User ──
    verification_token = secrets.token_urlsafe(32)
    
    user = User(
        id=str(uuid.uuid4()),  # UUID, not sequential integer
        email=email,
        password_hash=bcrypt.hashpw(
            password.encode(), bcrypt.gensalt(rounds=12)
        ).decode(),
        is_verified=False,
        verification_token=verification_token,
        verification_expires=datetime.utcnow() + timedelta(hours=24),
        created_ip=anonymize_ip(request.remote_addr),  # Store anonymized IP
        created_at=datetime.utcnow()
    )
    db.session.add(user)
    db.session.commit()
    
    # ── Step 7: Send Verification Email ──
    send_verification_email(
        to=email,
        token=verification_token,
        expires_in="24 hours"
    )
    
    # ── Step 8: Audit Log ──
    audit_log.info(
        f"Registration attempt | "
        f"email_prefix={email[:3]}*** | "
        f"ip={anonymize_ip(request.remote_addr)} | "
        f"user_agent={request.headers.get('User-Agent', 'unknown')[:50]}"
    )
    
    # ── Step 9: Same response regardless of outcome ──
    return {
        "message": "If this email is not already registered, "
                   "you will receive a verification email shortly."
    }, 200


def validate_password_strength(password: str) -> list:
    """Design Decision: Password policy enforcement"""
    errors = []
    if len(password) < 12:
        errors.append("Password must be at least 12 characters long")
    if len(password) > 128:
        errors.append("Password must not exceed 128 characters")
    if not re.search(r'[A-Z]', password):
        errors.append("Password must contain at least one uppercase letter")
    if not re.search(r'[a-z]', password):
        errors.append("Password must contain at least one lowercase letter")
    if not re.search(r'\d', password):
        errors.append("Password must contain at least one digit")
    if not re.search(r'[!@#$%^&*(),.?":{}|<>\-_=+\[\]\\;\'`~]', password):
        errors.append("Password must contain at least one special character")
    if re.search(r'(.)\1{2,}', password):
        errors.append("Password must not contain 3 or more repeated characters")
    return errors

Example 2: Secure File Upload Architecture

python
# ══════════════════════════════════════════════════════════════
# πŸ”΄ INSECURE DESIGN β€” Trusts everything from the client
# ══════════════════════════════════════════════════════════════

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']
    # Saves directly to web root with original filename
    # No size check, no type check, no malware scan
    # Path traversal possible: filename="../../etc/cron.d/backdoor"
    file.save(f'/var/www/html/uploads/{file.filename}')
    return {"url": f"https://example.com/uploads/{file.filename}"}


# ══════════════════════════════════════════════════════════════
# βœ… SECURE DESIGN β€” Defense at every step
# ══════════════════════════════════════════════════════════════

import uuid
import magic  # python-magic for MIME detection via magic bytes
from pathlib import Path
from werkzeug.utils import secure_filename

# Design Decisions:
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf'}
ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf'
}
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB
UPLOAD_DIR = Path('/var/data/uploads')  # OUTSIDE web root
QUARANTINE_DIR = Path('/var/data/quarantine')

@app.route('/api/files', methods=['POST'])
@require_auth
@limiter.limit("10 per hour")
def upload_file():
    # ── Step 1: Check if file exists in request ──
    file = request.files.get('file')
    if not file or not file.filename:
        return {"error": "No file provided"}, 400
    
    # ── Step 2: File size check (before reading entire file) ──
    file.seek(0, 2)  # Seek to end
    file_size = file.tell()
    file.seek(0)     # Seek back to beginning
    
    if file_size == 0:
        return {"error": "Empty file"}, 400
    if file_size > MAX_FILE_SIZE:
        return {
            "error": f"File too large. Maximum size: "
                     f"{MAX_FILE_SIZE // 1024 // 1024}MB"
        }, 400
    
    # ── Step 3: Extension whitelist check ──
    original_filename = secure_filename(file.filename)
    extension = Path(original_filename).suffix.lower()
    
    if extension not in ALLOWED_EXTENSIONS:
        audit_log.warning(
            f"Blocked file upload: extension '{extension}' "
            f"from user {current_user.id}"
        )
        return {"error": "File type not allowed"}, 400
    
    # ── Step 4: MIME type verification (magic bytes, not Content-Type header) ──
    header_bytes = file.read(2048)
    file.seek(0)
    detected_mime = magic.from_buffer(header_bytes, mime=True)
    
    if detected_mime not in ALLOWED_MIMES:
        audit_log.warning(
            f"MIME mismatch: claimed extension '{extension}', "
            f"detected MIME '{detected_mime}' "
            f"from user {current_user.id}"
        )
        return {"error": "File content does not match its extension"}, 400
    
    # ── Step 5: Generate safe filename (UUID-based) ──
    safe_filename = f"{uuid.uuid4().hex}{extension}"
    quarantine_path = QUARANTINE_DIR / safe_filename
    
    # ── Step 6: Save to quarantine first ──
    file.save(str(quarantine_path))
    
    # ── Step 7: Malware scan (ClamAV) ──
    if not scan_for_malware(quarantine_path):
        audit_log.critical(
            f"MALWARE DETECTED in upload from user {current_user.id} | "
            f"original_name: {original_filename}"
        )
        quarantine_path.unlink()  # Delete infected file
        alert_security_team(
            event="malware_upload",
            user_id=current_user.id,
            filename=original_filename
        )
        return {"error": "File rejected by security scan"}, 400
    
    # ── Step 8: Strip metadata (EXIF data can contain GPS, device info) ──
    strip_metadata(quarantine_path)
    
    # ── Step 9: Move from quarantine to permanent storage ──
    final_path = UPLOAD_DIR / safe_filename
    quarantine_path.rename(final_path)
    
    # ── Step 10: Re-encode images (neutralize image-based attacks) ──
    if detected_mime.startswith('image/'):
        reencode_image(final_path)
    
    # ── Step 11: Database record ──
    upload_record = FileUpload(
        id=str(uuid.uuid4()),
        user_id=current_user.id,
        original_name=original_filename[:255],
        stored_name=safe_filename,
        mime_type=detected_mime,
        size_bytes=file_size,
        sha256_hash=compute_sha256(final_path),
        uploaded_at=datetime.utcnow(),
        ip_address=anonymize_ip(request.remote_addr)
    )
    db.session.add(upload_record)
    db.session.commit()
    
    # ── Step 12: Audit log ──
    audit_log.info(
        f"File uploaded | id={upload_record.id} | "
        f"user={current_user.id} | "
        f"size={file_size} | mime={detected_mime}"
    )
    
    # ── Step 13: Return file access URL (via API, not direct path) ──
    return {
        "id": upload_record.id,
        "url": f"/api/files/{upload_record.id}",
        # ← Never expose the actual file path!
        # File is served through the API with authorization checks
        "size": file_size,
        "type": detected_mime
    }, 201

Example 3: API Authorization β€” Resource-Level Access Control

python
# ══════════════════════════════════════════════════════════════
# πŸ”΄ INSECURE DESIGN β€” No authorization, IDOR vulnerability
# ══════════════════════════════════════════════════════════════

@app.route('/api/invoices/<invoice_id>')
def get_invoice(invoice_id):
    # No authentication check
    # No authorization check
    # Sequential IDs allow enumeration
    invoice = Invoice.query.get(invoice_id)
    return jsonify(invoice.to_dict())
    # Attacker: GET /api/invoices/1, 2, 3, 4... β†’ dumps all invoices


# ══════════════════════════════════════════════════════════════
# βœ… SECURE DESIGN β€” Multi-layered authorization
# ══════════════════════════════════════════════════════════════

@app.route('/api/invoices/<invoice_id>')
@require_auth  # Layer 1: Authentication
@require_permission('invoice:read')  # Layer 2: Role-based permission
def get_invoice(invoice_id):
    # ── Layer 3: Input validation ──
    if not is_valid_uuid(invoice_id):
        return {"error": "Invalid invoice ID format"}, 400
    
    # ── Layer 4: Fetch resource ──
    invoice = Invoice.query.get(invoice_id)
    if not invoice:
        return {"error": "Invoice not found"}, 404
    
    # ── Layer 5: Resource-level authorization ──
    if not can_access_invoice(current_user, invoice):
        # Log the unauthorized access attempt with full context
        audit_log.warning(
            f"UNAUTHORIZED ACCESS ATTEMPT | "
            f"user={current_user.id} (role={current_user.role}) | "
            f"attempted_resource=invoice:{invoice_id} | "
            f"resource_owner={invoice.owner_id} | "
            f"ip={request.remote_addr}"
        )
        
        # Design Decision: Return 404, not 403
        # Returning 403 confirms the resource EXISTS
        # Returning 404 reveals nothing about resource existence
        return {"error": "Invoice not found"}, 404
    
    # ── Layer 6: Data filtering based on user's role ──
    # Different users see different fields
    response_data = invoice.to_filtered_dict(
        requesting_user=current_user,
        fields_policy=get_field_policy(current_user.role)
    )
    
    # ── Layer 7: Audit log (successful access) ──
    audit_log.info(
        f"Invoice accessed | invoice={invoice_id} | "
        f"user={current_user.id} | role={current_user.role}"
    )
    
    return jsonify(response_data), 200


def can_access_invoice(user, invoice) -> bool:
    """
    Resource-level authorization policy.
    
    Design Decision: Authorization rules are centralized here,
    not scattered across multiple endpoints.
    """
    # Rule 1: Owner can always access their own invoices
    if invoice.owner_id == user.id:
        return True
    
    # Rule 2: Finance managers can access invoices in their department
    if (user.has_role('finance_manager') 
            and invoice.department_id == user.department_id):
        return True
    
    # Rule 3: C-level executives can access all invoices (with logging)
    if user.has_role('executive'):
        audit_log.info(
            f"Executive access | user={user.id} | invoice={invoice.id}"
        )
        return True
    
    # Rule 4: External auditors can access invoices in their audit scope
    if (user.has_role('external_auditor') 
            and invoice.id in get_audit_scope(user.id)):
        return True
    
    # Default: DENY (fail secure)
    return False
  1. 🧰 Tools & Resources

Threat Modeling Tools

ToolTypeCostBest For
Microsoft Threat Modeling ToolDesktop AppFreeSTRIDE-based DFD modeling with automated threat generation
OWASP Threat DragonWeb / DesktopFree (Open Source)Lightweight, developer-friendly threat modeling
IriusRiskSaaS PlatformPaid (Enterprise)Automated threat modeling at scale, compliance mapping
ThreagileCLI ToolFree (Open Source)Threat model as code (YAML-based), CI/CD integration
draw.io / diagrams.netWeb AppFreeGeneral-purpose DFD and architecture diagrams
CAIRISWeb AppFree (Open Source)Risk analysis and security requirements management

Security Design Review Frameworks

FrameworkPurposeLink
OWASP ASVS v4.0Application Security Verification Standard β€” 286 security requirementsowasp.org/asvs
OWASP SAMMSoftware Assurance Maturity Model β€” measure your security practices maturityowasp.org/samm
NIST SP 800-160Systems Security Engineering β€” principles for secure system designcsrc.nist.gov
BSIMMBuilding Security In Maturity Model β€” benchmark against industrybsimm.com
Microsoft SDLSecurity Development Lifecycle β€” Microsoft's battle-tested practicesmicrosoft.com/sdl
SAFECodeFundamental Practices for Secure Software Developmentsafecode.org

SAST / DAST / SCA / IAST Tools

Recommended Reading & References

text
πŸ“š Essential Resources:
β”œβ”€β”€ OWASP Resources
β”‚   β”œβ”€β”€ OWASP Top 10 (2021)
β”‚   β”œβ”€β”€ OWASP ASVS v4.0 (Application Security Verification Standard)
β”‚   β”œβ”€β”€ OWASP Testing Guide v4.2
β”‚   β”œβ”€β”€ OWASP API Security Top 10
β”‚   β”œβ”€β”€ OWASP Cheat Sheet Series
β”‚   β”‚   β”œβ”€β”€ Authentication Cheat Sheet
β”‚   β”‚   β”œβ”€β”€ Authorization Cheat Sheet
β”‚   β”‚   β”œβ”€β”€ Session Management Cheat Sheet
β”‚   β”‚   β”œβ”€β”€ Input Validation Cheat Sheet
β”‚   β”‚   β”œβ”€β”€ Cryptographic Storage Cheat Sheet
β”‚   β”‚   β”œβ”€β”€ Error Handling Cheat Sheet
β”‚   β”‚   └── Threat Modeling Cheat Sheet
β”‚   └── OWASP Secure Coding Practices Quick Reference
β”‚
β”œβ”€β”€ Books
β”‚   β”œβ”€β”€ "Threat Modeling: Designing for Security" β€” Adam Shostack
β”‚   β”œβ”€β”€ "The Web Application Hacker's Handbook" β€” Stuttard & Pinto
β”‚   β”œβ”€β”€ "Secure by Design" β€” Dan Bergh Johnsson et al.
β”‚   β”œβ”€β”€ "Building Secure and Reliable Systems" β€” Google SRE
β”‚   └── "Security Engineering" β€” Ross Anderson
β”‚
β”œβ”€β”€ Standards & Frameworks
β”‚   β”œβ”€β”€ NIST SP 800-53 (Security Controls)
β”‚   β”œβ”€β”€ NIST SP 800-160 (Systems Security Engineering)
β”‚   β”œβ”€β”€ ISO 27001/27002 (Information Security Management)
β”‚   β”œβ”€β”€ CWE/SANS Top 25 Most Dangerous Software Weaknesses
β”‚   └── MITRE ATT&CK Framework
β”‚
└── Training & Practice
    β”œβ”€β”€ OWASP WebGoat (Hands-on vulnerable app)
    β”œβ”€β”€ OWASP Juice Shop (Modern vulnerable app)
    β”œβ”€β”€ PortSwigger Web Security Academy (Free)
    β”œβ”€β”€ HackTheBox / TryHackMe (Practice environments)
    └── Secure Code Warrior (Gamified secure coding)
  1. βœ… The Ultimate Checklist

πŸ” Secure Design Checklist

Use this checklist during every design review. If you cannot check every box, you have identified areas that need attention.

Architecture & Design

  • [ ] Threat model has been created and reviewed (STRIDE/PASTA)
  • [ ] Data Flow Diagrams (DFDs) exist with trust boundaries marked
  • [ ] Attack surface has been analyzed and minimized
  • [ ] Defense-in-depth strategy is documented and implemented
  • [ ] All components fail secure (not fail open)
  • [ ] Zero trust principles applied (verify everything explicitly)
  • [ ] Separation of duties enforced for critical operations
  • [ ] No security-through-obscurity dependencies
  • [ ] Third-party integrations risk-assessed
  • [ ] Disaster recovery and business continuity planned

Authentication

  • [ ] Multi-factor authentication (MFA) designed and implemented
  • [ ] Password policy enforced (12+ chars, complexity, breach check)
  • [ ] Brute-force protection (account lockout + rate limiting + CAPTCHA)
  • [ ] Secure password recovery flow (no security questions)
  • [ ] Credential stuffing defense (device fingerprinting, anomaly detection)
  • [ ] Session management (short timeout, rotation, secure cookies)
  • [ ] Account enumeration prevention (timing-safe, generic responses)
  • [ ] Passwordless authentication option considered (WebAuthn/FIDO2)

Authorization

  • [ ] RBAC/ABAC model designed and documented
  • [ ] Principle of Least Privilege enforced across all roles
  • [ ] Resource-level access control on every endpoint
  • [ ] Server-side authorization enforcement (never client-only)
  • [ ] Horizontal and vertical privilege escalation tested
  • [ ] Authorization bypass explicitly tested in abuse cases
  • [ ] Default deny policy (everything blocked unless explicitly allowed)
  • [ ] Centralized policy engine (OPA, Casbin, or equivalent)

Data Protection

  • [ ] Data classification completed (Public/Internal/Confidential/Restricted)
  • [ ] Encryption at rest (AES-256-GCM or equivalent)
  • [ ] Encryption in transit (TLS 1.3, strong cipher suites only)
  • [ ] PII/sensitive data masking in logs and responses
  • [ ] Secure key management (HSM, KMS, Vault β€” no hardcoded keys)
  • [ ] Data retention and deletion policies defined and automated
  • [ ] Backup encryption and testing
  • [ ] Data minimization (collect only what you need)

Input / Output

  • [ ] Server-side input validation on ALL endpoints (whitelist approach)
  • [ ] Output encoding (context-sensitive: HTML, JS, URL, CSS, SQL)
  • [ ] File upload restrictions (type, size, content verification, malware scan)
  • [ ] Content Security Policy (CSP) headers configured
  • [ ] All security headers set (HSTS, X-Frame-Options, X-Content-Type-Options)
  • [ ] API request/response schema validation (OpenAPI/JSON Schema)

Business Logic

  • [ ] Abuse cases written for every feature and tested
  • [ ] Rate limiting on all critical endpoints (login, registration, API)
  • [ ] Transaction integrity guaranteed (race conditions addressed)
  • [ ] Idempotency designed into financial/state-changing operations
  • [ ] Workflow bypass explicitly tested (can steps be skipped?)
  • [ ] Negative testing performed (negative values, zero values, max values)
  • [ ] Concurrent request handling tested

Monitoring & Incident Response

  • [ ] Comprehensive audit logging designed (who, what, when, where)
  • [ ] Logs are immutable and tamper-proof (append-only, signed)
  • [ ] Security alerting mechanism configured (real-time)
  • [ ] Incident response plan documented and rehearsed
  • [ ] Log retention policy defined
  • [ ] No sensitive data in logs (passwords, tokens, PII)
  • [ ] Failed authentication and authorization attempts logged and alerted
  1. 🏁 Conclusion

The Core Message

Key Takeaways

πŸ”΄ Insecure Design is not a code bug β€” it is a thinking bug.
It represents the absence of security considerations during the most critical phase of software development.
🟑 Perfect implementation can NEVER compensate for a flawed design.
You cannot patch a missing wall. You have to rebuild.
🟒 Threat Modeling + Secure Design Patterns + Abuse Cases = Secure Systems.
These three practices, applied consistently, eliminate the vast majority of design-level vulnerabilities.
πŸ”΅ Security is not a feature β€” it is a property of the system.
It must be woven into the fabric of the architecture, not bolted on as an afterthought.

The Three Questions

Every time you design a new feature, endpoint, workflow, or system β€” ask yourself these three questions:

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                                             β”‚
β”‚  1. πŸ€” "How will an ATTACKER abuse this feature?"           β”‚
β”‚     β†’ Write abuse cases. Think adversarially.               β”‚
β”‚                                                             β”‚
β”‚  2. πŸ€” "What happens when this feature FAILS or ERRORS?"    β”‚
β”‚     β†’ Design for failure. Fail secure, not open.            β”‚
β”‚                                                             β”‚
β”‚  3. πŸ€” "What is the MINIMUM permission needed for this?"    β”‚
β”‚     β†’ Apply least privilege. Default deny.                  β”‚
β”‚                                                             β”‚
β”‚  The moment you start asking these questions consistently,  β”‚
β”‚  you are thinking SECURE BY DESIGN. 🎯                      β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The Cost of Inaction

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        BREACH COST BREAKDOWN                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                    β”‚
β”‚  Average cost of a data breach (2024):           $4.88 MILLION    β”‚
β”‚  Average cost if secure design was applied:      $0.15 MILLION    β”‚
β”‚                                                                    β”‚
β”‚  Time to identify a breach (avg):                194 DAYS          β”‚
β”‚  Time to contain a breach (avg):                 64 DAYS           β”‚
β”‚                                                                    β”‚
β”‚  Breaches caused by design flaws:                ~40%              β”‚
β”‚  Breaches preventable by threat modeling:        ~60%              β”‚
β”‚                                                                    β”‚
β”‚  Sources: IBM Cost of a Data Breach Report 2024, Ponemon Institute β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Final Words

Security is not something you add at the end. It is not a sprint task you defer. It is not a checkbox on a compliance form. It is the foundation upon which reliable, trustworthy software is built.

Insecure Design (A04:2021) exists in the OWASP Top 10 as a reminder that no amount of firewalls, WAFs, encryption, or monitoring can save a system that was fundamentally designed to be insecure.

Start with the design. Start with the threat model. Start with the abuse cases.

Everything else follows.

<div align="center">

πŸ“š Further Reading

ResourceURL
OWASP Top 10 β€” 2021https://owasp.org/Top10/
OWASP ASVS v4.0https://owasp.org/www-project-application-security-verification-standard/
OWASP Threat Dragonhttps://owasp.org/www-project-threat-dragon/
OWASP Cheat Sheet Serieshttps://cheatsheetseries.owasp.org/
NIST SSDFhttps://csrc.nist.gov/Projects/ssdf
MITRE CWEhttps://cwe.mitre.org/
HaveIBeenPwnedhttps://haveibeenpwned.com/

Category: Application Security Β· Secure Architecture Β· OWASP Tags: #OWASP #InsecureDesign #ThreatModeling #SecureSDLC #AppSec #SecurityArchitecture #A04

"Security is not a product, but a process." β€” Bruce Schneier
"The only truly secure system is one that is powered off, cast in a block of concrete, and sealed in a lead-lined room with armed guards β€” and even then I have my doubts." β€” Gene Spafford
"If you think technology can solve your security problems, then you don't understand the problems and you don't understand the technology." β€” Bruce Schneier

</div>