Secure Development Lifecycle (SSDLC) â Build Security Into Every Phase
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
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