Skip to content

Secure Development Lifecycle (SSDLC) — Build Security Into Every Phase

DodaTech Updated 2026-06-22 9 min read

In this tutorial, you'll learn about Secure Development Lifecycle (SSDLC). We cover key concepts, practical examples, and best practices.

Master the Secure Software Development Lifecycle (SSDLC): threat modeling, secure design, security testing, code review, and continuous monitoring integrated into agile development workflows.

What You'll Learn

You will learn to integrate security practices into every phase of software development -- from requirements and design through implementation, testing, deployment, and maintenance -- following the SSDLC framework used by security-conscious organizations worldwide.

Why It Matters

Fixing a security vulnerability in production costs 30 times more than addressing it during design. The average data breach costs $4.45 million. A mature SSDLC program catches 90% of vulnerabilities before code reaches production, reducing both cost and risk. Building security in from the start is not optional -- it is engineering best practice.

Real-World Use

When the Doda Browser team added a new password manager feature, the SSDLC process required a threat modeling session during design, static analysis on every pull request, a focused penetration test before release, and ongoing monitoring for unusual access patterns after launch. The threat model identified a clipboard exposure risk that was fixed in design, before any code was written.

Your Learning Path

flowchart LR
  A[Application Security] --> B[SSDLC]
  B --> C[Secure Coding]
  C --> D[Security Testing]
  D --> E[Incident Response]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff
â„šī¸ Info

Prerequisites: Understanding of the Application Security -- OWASP Top 10 Top 10 vulnerabilities and basic secure coding principles. Familiarity with agile development workflows.

SSDLC Phases Overview

Phase Security Activity Deliverable
Requirements Security requirements gathering Security requirements document
Design Threat modeling Threat model diagram
Implementation Secure coding + SAST Clean SAST scan
Testing DAST + Penetration testing Security test report
Deployment Hardening + Configuration review Hardening checklist
Maintenance Vulnerability monitoring Patch cadence

Phase 1: Requirements

Security requirements must be defined alongside functional requirements. Every feature should answer: "What security properties does this feature need?"

Feature: User password reset
Security requirements:
  - REQ-SEC-01: Reset link must expire after 15 minutes
  - REQ-SEC-02: Reset link must be single-use
  - REQ-SEC-03: New password must meet complexity policy
  - REQ-SEC-04: Email notification sent on password change
  - REQ-SEC-05: Rate limit: max 3 reset requests per hour per account
  - REQ-SEC-06: No information disclosure about account existence

Expected behavior: Security requirements are documented in the same format as functional requirements, tracked in the same project management tool, and reviewed during sprint planning.

Phase 2: Design and Threat Modeling

Threat modeling identifies security risks before code is written. The STRIDE model (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) provides a structured approach.

flowchart TD
  A[User Browser] -->|HTTPS| B[Load Balancer]
  B --> C[Web Server]
  C --> D[Application Server]
  D --> E[(Database)]
  D --> F[Cache Redis]
  D --> G[Queue RabbitMQ]

  H[Attacker] -.->|Spoofing| A
  H -.->|MITM| B
  H -.->|SQL Injection| C
  H -.->|Session Hijack| D

  style H fill:#c44,color:#fff
  style A fill:#4a9,color:#fff
  style B fill:#49a,color:#fff
  style C fill:#47a,color:#fff

Threat Modeling Template

# threat-model.yaml
feature: User Authentication
version: 1.0
date: 2026-06-22

assets:
  - name: User credentials
    classification: confidential
  - name: Session tokens
    classification: confidential
  - name: Audit logs
    classification: internal

threats:
  - id: T-001
    description: Brute force password guessing
    category: Spoofing
    risk: High
    mitigation: Rate limiting, account lockout after 5 attempts, CAPTCHA
    status: Accepted (rate limiting implemented)

  - id: T-002
    description: SQL injection in login query
    category: Tampering
    risk: Critical
    mitigation: Parameterized queries, input validation, WAF rules
    status: Mitigated

  - id: T-003
    description: Session token theft via XSS
    category: Information Disclosure
    risk: High
    mitigation: HttpOnly + Secure cookies, CSP header, XSS sanitization
    status: Mitigated

Expected behavior: The threat model is created during design, reviewed by the security team, and updated as the feature evolves. Each threat has an owner and a mitigation status.

Phase 3: Implementation

During implementation, developers follow secure coding standards and run static analysis before committing.

# Secure coding: input validation example
import re
from flask import request, jsonify, session
import bleach

def update_profile():
    """SSDLC-compliant profile update endpoint."""
    user_id = session.get("user_id")
    if not user_id:
        return jsonify({"error": "Unauthorized"}), 401

    display_name = request.form.get("display_name", "")
    bio = request.form.get("bio", "")

    # Input validation
    if len(display_name) > 50:
        return jsonify({"error": "Display name too long"}), 400
    if not re.match(r"^[a-zA-Z0-9 _-]+$", display_name):
        return jsonify({"error": "Invalid characters in name"}), 400

    # Sanitize HTML in bio
    sanitized_bio = bleach.clean(
        bio,
        tags=["b", "i", "em", "strong", "a"],
        attributes={"a": ["href", "rel"]},
        strip=True
    )

    # Parameterized database query
    cursor.execute(
        "UPDATE users SET display_name = %s, bio = %s WHERE id = %s",
        (display_name, sanitized_bio, user_id)
    )
    conn.commit()

    return jsonify({"status": "updated"})

Expected behavior: Each security control (authentication, input validation, HTML sanitization, parameterized query) maps directly to a requirement or threat model entry from the design phase.

Static Analysis Configuration

# .semgrep.yml
rules:
  - id: sql-injection
    patterns:
      - pattern: |
          $QUERY = "...$VAR..."
      - pattern-not: |
          $QUERY = "...%s..."
    message: "Possible SQL injection. Use parameterized queries."
    severity: ERROR

  - id: hardcoded-secret
    patterns:
      - pattern-either:
          - pattern: |
              $KEY = "..."
          - pattern: |
              $TOKEN = "..."
      - metavariable-regex:
          metavariable: $KEY
          regex: "(?i)(password|secret|key|token|api_key)"
    message: "Hardcoded secret detected. Use environment variables."
    severity: ERROR

Expected behavior: Semgrep runs on every pull request. Rules catching SQL injection and hardcoded secrets block the PR from merging until fixed.

Phase 4: Testing

Security testing combines automated scanning with manual review.

# Automated security test example
import requests
import pytest

BASE_URL = "https://staging.example.com"

class TestSecurityEndpoints:

    def test_sql_injection_on_login(self):
        """Attempt SQL injection on login endpoint."""
        payloads = [
            "' OR '1'='1",
            "'; DROP TABLE users; --",
            "' UNION SELECT * FROM users; --",
            "admin'--",
        ]
        for payload in payloads:
            response = requests.post(
                f"{BASE_URL}/api/login",
                json={"username": payload, "password": payload},
            )
            # If any injection succeeds, this returns 200
            assert response.status_code in (401, 400), \
                f"Injection succeeded with payload: {payload}"

    def test_idor_on_user_profile(self):
        """Test for insecure direct object reference."""
        # Authenticate as user A
        auth = requests.post(
            f"{BASE_URL}/api/login",
            json={"username": "user_a", "password": "password_a"},
        )
        token = auth.json()["token"]
        headers = {"Authorization": f"Bearer {token}"}

        # Try to access user B's profile
        response = requests.get(
            f"{BASE_URL}/api/users/42/profile",
            headers=headers,
        )
        assert response.status_code == 403, \
            "User A should not access User B's profile"

    def test_rate_limiting(self):
        """Verify rate limiting on password reset."""
        for i in range(10):
            response = requests.post(
                f"{BASE_URL}/api/reset-password",
                json={"email": "test@example.com"},
            )
        # After multiple requests, should be throttled
        assert response.status_code == 429, \
            "Rate limiting should return 429 after threshold"

Expected behavior: Automated security tests run in CI/CD. SQL injection attempts are blocked, IDOR returns 403, and rate limiting kicks in after the configured threshold.

Phase 5: Deployment

Security hardening checklists ensure consistent deployment configuration.

# deployment-hardening.yml
production_hardening:
  tls:
    min_version: "TLS 1.2"
    ciphers: "ECDHE+AESGCM:ECDHE+CHACHA20"
    hsts_max_age: 31536000
    hsts_include_subdomains: true

  headers:
    content_security_policy: "default-src 'self'; script-src 'self'"
    x_content_type_options: "nosniff"
    x_frame_options: "DENY"
    referrer_policy: "strict-origin-when-cross-origin"

  application:
    debug_mode: false
    error_handling: "custom (no stack traces)"
    session_secure: true
    session_httponly: true
    session_samesite: "Lax"

  infrastructure:
    database_encryption: true
    backup_encryption: true
    logging_level: "INFO"
    audit_logging: true

Expected behavior: The hardening checklist is verified before every production deployment. Any deviation blocks the release.

Phase 6: Maintenance

Continuous monitoring and vulnerability management keep the application secure after deployment.

import subprocess
import json
from datetime import datetime

def check_vulnerabilities():
    """Run dependency vulnerability scan."""
    result = subprocess.run(
        ["pip-audit", "--format", "json"],
        capture_output=True,
        text=True,
    )
    vulnerabilities = json.loads(result.stdout)

    for vuln in vulnerabilities.get("vulnerabilities", []):
        severity = vuln.get("severity", "unknown")
        print(f"[{severity.upper()}] {vuln['name']} {vuln['version']}")
        print(f"  Advisory: {vuln['advisory']}")
        print(f"  Fix: upgrade to {vuln['fixed_version']}")

    # Alert on critical vulnerabilities
    critical = [
        v for v in vulnerabilities.get("vulnerabilities", [])
        if v.get("severity") == "critical"
    ]
    if critical:
        print("\nCRITICAL VULNERABILITIES FOUND!")
        print("Alerting security team...")
        # send_alert(critical)

    return len(vulnerabilities.get("vulnerabilities", []))

count = check_vulnerabilities()
print(f"Found {count} vulnerabilities")

Expected behavior: The vulnerability scanner runs daily via cron. Critical findings trigger an alert to the security team. Patches are applied within the SLA window (critical: 24 hours, high: 7 days).

Common SSDLC Mistakes

1. Treating Security as a Separate Phase

Security is not a gate at the end of development. It must be integrated into every phase. Waiting until "security testing" to find vulnerabilities guarantees late-stage rework.

2. Skipping Threat Modeling for Small Features

Even a small feature can introduce security risks. A simple "feedback form" feature could allow XSS, CSRF, and data leakage if not properly threat-modeled.

3. Using Only Automated Tools

SAST and DAST tools miss logical vulnerabilities, business logic flaws, and complex attack chains. Manual review and penetration testing are essential complements.

4. Not Updating Threat Models

Threat models created during design become stale as features evolve. Update them when requirements change, new attack vectors emerge, or after security incidents.

5. Ignoring Third-Party Dependencies

Modern applications use hundreds of dependencies. Each one is a potential attack vector. Maintain a software bill of materials (SBOM) and scan regularly.

6. No Security Champion Program

Security expertise concentrated in a small team creates a bottleneck. Train security champions in each development team to distribute knowledge and ownership.

7. Treating Compliance as Security

Passing a compliance audit (SOC 2, ISO 27001) does not mean your application is secure. Compliance is a baseline, not a destination. Build beyond the minimum requirements.

Practice Questions

1. What is the purpose of threat modeling in the SSDLC? Threat modeling identifies security risks during the design phase, before code is written. It ensures security controls are planned rather than retrofitted, reducing cost and risk.

2. Why is fixing a vulnerability in production 30 times more expensive than fixing it during design? In production, you must patch deployed systems, coordinate downtime, roll back if issues occur, communicate with users, and potentially disclose the vulnerability. During design, you change a document.

3. What is the difference between SAST and DAST? SAST (Static Application Security Testing) analyzes source code without executing it. DAST (Dynamic Application Security Testing) tests the running application from the outside. SAST catches implementation bugs early; DAST catches runtime and configuration issues.

4. Why should every team have a security champion? Security champions distribute security knowledge across teams, reduce bottlenecks on the central security team, and catch issues earlier in the development process.

5. Challenge: Perform a threat model for a simple feature (like a file upload endpoint) using the STRIDE methodology. Document at least five threats with their mitigations and risk levels.

Mini Project: SSDLC Implementation Plan

Create an SSDLC implementation plan for a fictional application (or use one of the DodaTech products). For each of the six phases, document: the security activities to perform, the tools to use, the team members responsible, the deliverables expected, and the acceptance criteria. Include a timeline for rolling out the SSDLC process across three development teams over six months.

# ssdlc-implementation-plan.yaml
project: DodaZIP Cloud Sync
timeline: Q3-Q4 2026

phases:
  - phase: Requirements
    activities:
      - Security requirements workshop
      - Data classification
    tools:
      - Jira (requirements tracking)
    owner: Product Manager
    deliverable: Security Requirements Document
    acceptance: All features have security req IDs

  - phase: Design
    activities:
      - Threat modeling session
      - Architecture review
    tools:
      - OWASP Threat Dragon
      - draw.io
    owner: Lead Developer
    deliverable: Threat Model Diagram
    acceptance: Every STRIDE category addressed

Expected behavior: The plan is reviewed and approved by engineering and security leadership. Progress is tracked monthly with a dashboard showing completed phases, open threats, and vulnerability trends.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro