Security is not a feature—it's a foundation. This comprehensive guide covers essential security practices that every development team should integrate throughout the software development lifecycle, from design through deployment and beyond.

The Security-First Mindset

In modern software development, security is everyone's responsibility. The cost of fixing a vulnerability increases exponentially the later it's discovered—finding an issue in production costs 100x more than catching it during design.

mindmap root((Secure Development)) Design Threat Modeling Security Requirements Architecture Review Data Classification Code Secure Coding Code Review SAST Analysis Dependency Scanning Test Security Testing DAST Scanning Penetration Testing Fuzzing Deploy Secure Config Secrets Management Container Security Infrastructure as Code Operate Monitoring Incident Response Vulnerability Mgmt Security Updates
Shift Left Security

The earlier you find security issues, the cheaper they are to fix. A vulnerability found in design costs $500 to fix; the same vulnerability in production costs $15,000+.

Secure Development Lifecycle (SDL)

Integrate security into every phase of development, not as an afterthought but as a core practice.

flowchart LR subgraph Design["1. Design"] TM[Threat Modeling] SR[Security Requirements] AR[Architecture Review] end subgraph Develop["2. Develop"] SC[Secure Coding] CR[Code Review] SAST[SAST Scanning] end subgraph Build["3. Build"] SCA[Dependency Scan] Container[Container Scan] IaC[IaC Security] end subgraph Test["4. Test"] DAST[DAST Scanning] Pentest[Penetration Test] Fuzz[Fuzzing] end subgraph Deploy["5. Deploy"] Secrets[Secrets Mgmt] Config[Secure Config] Monitor[Monitoring] end Design --> Develop Develop --> Build Build --> Test Test --> Deploy Deploy -.->|Feedback| Design style TM fill:#ff6b6b,color:#fff style SAST fill:#4ecdc4,color:#fff style DAST fill:#45b7d1,color:#fff style Secrets fill:#96ceb4,color:#fff

Traditional vs DevSecOps Approach

Aspect Traditional Security DevSecOps
Timing Before deployment (late stage) Throughout development (continuous)
Responsibility Dedicated security team Shared by all team members
Release Impact Slows releases, creates bottlenecks Enables fast, secure releases
Automation Manual reviews and audits Automated scanning in CI/CD
Feedback Loop Late feedback, high rework cost Immediate feedback, low fix cost
Culture Security as gatekeeper Security as enabler

OWASP Top 10: Critical Vulnerabilities

The OWASP Top 10 represents the most critical security risks to web applications. Every developer should understand and prevent these vulnerabilities.

flowchart TB subgraph A01["A01: Broken Access Control"] A01_D[Users accessing unauthorized functions or data] end subgraph A02["A02: Cryptographic Failures"] A02_D[Weak encryption, exposed sensitive data] end subgraph A03["A03: Injection"] A03_D[SQL, NoSQL, OS, LDAP injection attacks] end subgraph A04["A04: Insecure Design"] A04_D[Missing security controls in architecture] end subgraph A05["A05: Security Misconfiguration"] A05_D[Default configs, unnecessary features, verbose errors] end A01 --> Prevention1[Role-based access, deny by default] A02 --> Prevention2[TLS 1.3, strong algorithms, key management] A03 --> Prevention3[Parameterized queries, input validation] A04 --> Prevention4[Threat modeling, secure design patterns] A05 --> Prevention5[Hardening, minimal install, automation] style A01 fill:#ff6b6b,color:#fff style A02 fill:#ff8c42,color:#fff style A03 fill:#ffd93d,color:#333 style A04 fill:#6bcb77,color:#fff style A05 fill:#4d96ff,color:#fff

OWASP Top 10 Prevention Guide

Vulnerability Description Prevention
A01: Broken Access Control Users act outside intended permissions Deny by default, enforce ownership, rate limiting
A02: Cryptographic Failures Exposure of sensitive data Encrypt in transit/rest, strong algorithms, key rotation
A03: Injection Untrusted data sent to interpreter Parameterized queries, input validation, escaping
A04: Insecure Design Missing security controls Threat modeling, secure design patterns, security requirements
A05: Security Misconfiguration Insecure default configurations Hardened configs, minimal install, automated checks
A06: Vulnerable Components Using components with known vulns SCA scanning, dependency updates, SBOM
A07: Auth Failures Broken authentication mechanisms MFA, strong passwords, rate limiting, secure sessions
A08: Integrity Failures Code/data integrity not verified Digital signatures, integrity checks, secure CI/CD
A09: Logging Failures Insufficient logging and monitoring Audit logs, alerting, log integrity, SIEM integration
A10: SSRF Server makes requests to attacker-controlled URLs URL allowlists, disable redirects, network segmentation

Secure Coding Practices

Writing secure code requires understanding common vulnerability patterns and applying defensive programming techniques.

Input Validation and Sanitization

Critical Rule

Never trust user input. All input is potentially malicious until validated. Validate on the server side—client-side validation is for UX, not security.

# Secure input validation example
import re
from typing import Optional
from dataclasses import dataclass
from enum import Enum

class ValidationError(Exception):
    """Custom validation error with safe messages."""
    pass

class InputType(Enum):
    EMAIL = "email"
    USERNAME = "username"
    PHONE = "phone"
    URL = "url"

@dataclass
class ValidationRule:
    pattern: str
    min_length: int
    max_length: int
    error_message: str

# Define strict validation rules
VALIDATION_RULES = {
    InputType.EMAIL: ValidationRule(
        pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
        min_length=5,
        max_length=254,
        error_message="Invalid email format"
    ),
    InputType.USERNAME: ValidationRule(
        pattern=r'^[a-zA-Z][a-zA-Z0-9_]{2,29}$',
        min_length=3,
        max_length=30,
        error_message="Username must start with letter, 3-30 chars"
    ),
    InputType.PHONE: ValidationRule(
        pattern=r'^\+?[1-9]\d{1,14}$',
        min_length=10,
        max_length=15,
        error_message="Invalid phone number format"
    )
}

def validate_input(value: str, input_type: InputType) -> str:
    """
    Validate and sanitize input based on type.
    Returns sanitized value or raises ValidationError.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("Input is required")

    # Strip and normalize
    value = value.strip()

    rule = VALIDATION_RULES.get(input_type)
    if not rule:
        raise ValidationError("Unknown input type")

    # Check length
    if len(value) < rule.min_length or len(value) > rule.max_length:
        raise ValidationError(rule.error_message)

    # Check pattern
    if not re.match(rule.pattern, value):
        raise ValidationError(rule.error_message)

    return value

def sanitize_html(content: str) -> str:
    """
    Remove potentially dangerous HTML/JS content.
    Use a proper library like bleach in production.
    """
    import html
    # Escape HTML entities
    sanitized = html.escape(content)
    # Remove script-related patterns
    dangerous_patterns = [
        r'javascript:',
        r'on\w+\s*=',
        r'

SQL Injection Prevention

1

Use Parameterized Queries

Never concatenate user input into SQL queries. Always use parameterized queries or prepared statements.

2

Use ORM Safely

ORMs provide protection, but raw queries within ORMs can still be vulnerable. Review all raw SQL usage.

3

Apply Least Privilege

Database users should have minimum required permissions. Don't use admin accounts for application connections.

# SQL Injection Prevention Examples
import sqlite3
from typing import List, Optional, Dict, Any

class SecureDatabase:
    """Database class with secure query methods."""

    def __init__(self, db_path: str):
        self.conn = sqlite3.connect(db_path)
        self.conn.row_factory = sqlite3.Row

    # ❌ VULNERABLE - Never do this!
    def get_user_vulnerable(self, username: str):
        """DANGEROUS: SQL Injection vulnerable."""
        query = f"SELECT * FROM users WHERE username = '{username}'"
        return self.conn.execute(query).fetchone()

    # ✅ SECURE - Use parameterized queries
    def get_user_secure(self, username: str) -> Optional[Dict[str, Any]]:
        """SAFE: Uses parameterized query."""
        query = "SELECT id, username, email FROM users WHERE username = ?"
        result = self.conn.execute(query, (username,)).fetchone()
        return dict(result) if result else None

    # ✅ SECURE - Multiple parameters
    def search_users(self, name: str, role: str, limit: int = 10) -> List[Dict]:
        """SAFE: Multiple parameterized values."""
        query = """
            SELECT id, username, email, role
            FROM users
            WHERE name LIKE ? AND role = ?
            LIMIT ?
        """
        # Use wildcards safely in the parameter, not the query
        results = self.conn.execute(query, (f"%{name}%", role, limit))
        return [dict(row) for row in results.fetchall()]

    # ✅ SECURE - Dynamic column selection (whitelisted)
    def get_users_sorted(self, sort_column: str, order: str = "ASC"):
        """SAFE: Whitelist approach for dynamic SQL parts."""
        # Whitelist allowed columns
        allowed_columns = {"id", "username", "created_at", "email"}
        allowed_orders = {"ASC", "DESC"}

        if sort_column not in allowed_columns:
            raise ValueError(f"Invalid sort column: {sort_column}")
        if order.upper() not in allowed_orders:
            raise ValueError(f"Invalid sort order: {order}")

        # Safe because values are validated against whitelist
        query = f"SELECT * FROM users ORDER BY {sort_column} {order}"
        return self.conn.execute(query).fetchall()

Authentication Security

# Secure password handling
import secrets
import hashlib
from typing import Tuple
import bcrypt

class PasswordSecurity:
    """Secure password hashing and verification."""

    # Cost factor - increase for more security (slower)
    BCRYPT_ROUNDS = 12

    @staticmethod
    def hash_password(password: str) -> str:
        """
        Hash password using bcrypt with salt.
        Returns the hash string for storage.
        """
        # Encode password to bytes
        password_bytes = password.encode('utf-8')

        # Generate salt and hash
        salt = bcrypt.gensalt(rounds=PasswordSecurity.BCRYPT_ROUNDS)
        hashed = bcrypt.hashpw(password_bytes, salt)

        return hashed.decode('utf-8')

    @staticmethod
    def verify_password(password: str, hashed: str) -> bool:
        """
        Verify password against stored hash.
        Uses constant-time comparison to prevent timing attacks.
        """
        try:
            password_bytes = password.encode('utf-8')
            hashed_bytes = hashed.encode('utf-8')
            return bcrypt.checkpw(password_bytes, hashed_bytes)
        except Exception:
            return False

    @staticmethod
    def check_password_strength(password: str) -> Tuple[bool, List[str]]:
        """
        Check if password meets security requirements.
        Returns (is_valid, list_of_issues).
        """
        issues = []

        if len(password) < 12:
            issues.append("Password must be at least 12 characters")
        if len(password) > 128:
            issues.append("Password must not exceed 128 characters")
        if not any(c.isupper() for c in password):
            issues.append("Password must contain uppercase letter")
        if not any(c.islower() for c in password):
            issues.append("Password must contain lowercase letter")
        if not any(c.isdigit() for c in password):
            issues.append("Password must contain a number")
        if not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
            issues.append("Password must contain a special character")

        # Check against common passwords
        common_passwords = {"password", "123456", "qwerty", "admin"}
        if password.lower() in common_passwords:
            issues.append("Password is too common")

        return len(issues) == 0, issues

    @staticmethod
    def generate_secure_token(length: int = 32) -> str:
        """Generate cryptographically secure random token."""
        return secrets.token_urlsafe(length)

    @staticmethod
    def generate_reset_token() -> Tuple[str, str]:
        """
        Generate password reset token.
        Returns (token_for_user, hash_for_storage).
        """
        token = secrets.token_urlsafe(32)
        token_hash = hashlib.sha256(token.encode()).hexdigest()
        return token, token_hash

Cross-Site Scripting (XSS) Prevention

// XSS Prevention in JavaScript

// ❌ VULNERABLE - Never do this!
function displayUserInputDangerous(userInput) {
  document.getElementById('output').innerHTML = userInput;
  // Attacker can inject: 
}

// ✅ SECURE - Use textContent for plain text
function displayUserInputSafe(userInput) {
  document.getElementById('output').textContent = userInput;
  // Scripts are rendered as text, not executed
}

// ✅ SECURE - Sanitize if HTML is needed
function displaySanitizedHTML(userInput) {
  const sanitized = DOMPurify.sanitize(userInput, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href', 'title'],
    ALLOW_DATA_ATTR: false
  });
  document.getElementById('output').innerHTML = sanitized;
}

// ✅ SECURE - Create elements programmatically
function createUserElement(username, email) {
  const div = document.createElement('div');
  div.className = 'user-card';

  const nameSpan = document.createElement('span');
  nameSpan.textContent = username; // Safe - textContent escapes

  const emailLink = document.createElement('a');
  emailLink.textContent = email;
  emailLink.href = `mailto:${encodeURIComponent(email)}`;

  div.appendChild(nameSpan);
  div.appendChild(emailLink);

  return div;
}

// ✅ SECURE - Template literals with proper escaping
function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&")
    .replace(//g, ">")
    .replace(/"/g, """)
    .replace(/'/g, "'");
}

// Content Security Policy header example
// Set this in your server configuration
const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-${generateNonce()}';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
`;

Secrets Management

Never hardcode secrets. Use environment variables and dedicated secrets management solutions.

Never Commit Secrets

API keys, passwords, tokens, and certificates should never appear in source code. A single exposed secret can compromise your entire system.

flowchart LR subgraph Wrong["❌ Insecure"] Code[Source Code] Repo[Git Repository] Code --> Repo end subgraph Right["✅ Secure"] Vault[Secrets Manager] Env[Environment Variables] App[Application] Vault --> Env Env --> App end style Wrong fill:#ffcccc style Right fill:#ccffcc style Vault fill:#4caf50,color:#fff

Secrets Management Best Practices

# Secure secrets management
import os
from functools import lru_cache
from typing import Optional
import boto3
from botocore.exceptions import ClientError
import json

class SecretsManager:
    """
    Centralized secrets management.
    Supports environment variables and AWS Secrets Manager.
    """

    def __init__(self, use_aws: bool = False, region: str = "us-east-1"):
        self.use_aws = use_aws
        if use_aws:
            self.client = boto3.client('secretsmanager', region_name=region)
        self._cache = {}

    def get_secret(self, key: str, required: bool = True) -> Optional[str]:
        """
        Get secret from environment or secrets manager.
        Caches values to minimize API calls.
        """
        # Check cache first
        if key in self._cache:
            return self._cache[key]

        value = None

        # Try environment variable first
        value = os.environ.get(key)

        # Fall back to AWS Secrets Manager
        if value is None and self.use_aws:
            value = self._get_aws_secret(key)

        if value is None and required:
            raise ValueError(f"Required secret '{key}' not found")

        # Cache the value
        if value:
            self._cache[key] = value

        return value

    def _get_aws_secret(self, secret_name: str) -> Optional[str]:
        """Retrieve secret from AWS Secrets Manager."""
        try:
            response = self.client.get_secret_value(SecretId=secret_name)

            if 'SecretString' in response:
                secret = response['SecretString']
                # Handle JSON secrets
                try:
                    parsed = json.loads(secret)
                    return parsed.get('value', secret)
                except json.JSONDecodeError:
                    return secret
            return None

        except ClientError as e:
            if e.response['Error']['Code'] == 'ResourceNotFoundException':
                return None
            raise

    def rotate_secret(self, key: str, new_value: str) -> bool:
        """Rotate a secret value."""
        if self.use_aws:
            try:
                self.client.put_secret_value(
                    SecretId=key,
                    SecretString=new_value
                )
                # Clear cache
                self._cache.pop(key, None)
                return True
            except ClientError:
                return False
        return False


# Usage example
secrets = SecretsManager(use_aws=True)

# Get database credentials
db_host = secrets.get_secret('DB_HOST')
db_password = secrets.get_secret('DB_PASSWORD')
api_key = secrets.get_secret('API_KEY')

Pre-commit Secret Detection

# .pre-commit-config.yaml
repos:
  # Detect secrets before commit
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']
        exclude: package-lock.json

  # Prevent private keys
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: detect-private-key
      - id: check-added-large-files
        args: ['--maxkb=500']

  # Git-secrets for AWS credentials
  - repo: https://github.com/awslabs/git-secrets
    rev: master
    hooks:
      - id: git-secrets

Secure CI/CD Pipeline

Automate security checks throughout your CI/CD pipeline to catch vulnerabilities before deployment.

flowchart TB subgraph Stage1["Stage 1: Pre-Commit"] Lint[Linting] Secrets[Secret Detection] Format[Code Formatting] end subgraph Stage2["Stage 2: Build"] SAST[SAST Scan] SCA[Dependency Scan] License[License Check] end subgraph Stage3["Stage 3: Test"] Unit[Unit Tests] Integration[Integration Tests] Security[Security Tests] end subgraph Stage4["Stage 4: Package"] Build[Build Artifacts] Container[Container Scan] Sign[Sign Artifacts] end subgraph Stage5["Stage 5: Deploy"] IaC[IaC Scan] DAST[DAST Scan] Monitor[Enable Monitoring] end Stage1 --> Stage2 Stage2 --> Stage3 Stage3 --> Stage4 Stage4 --> Stage5 style SAST fill:#ff6b6b,color:#fff style SCA fill:#4ecdc4,color:#fff style Container fill:#45b7d1,color:#fff style DAST fill:#96ceb4,color:#fff

Complete Security Pipeline

# .github/workflows/security-pipeline.yml
name: Security Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    # Run weekly security scan
    - cron: '0 0 * * 0'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # Stage 1: Static Analysis
  sast:
    name: Static Application Security Testing
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: javascript, python

      - name: Run CodeQL Analysis
        uses: github/codeql-action/analyze@v2

      - name: Run Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/secrets
            p/owasp-top-ten

  # Stage 2: Dependency Scanning
  sca:
    name: Software Composition Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/node@master
        continue-on-error: true
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

      - name: Run OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: '${{ github.repository }}'
          path: '.'
          format: 'SARIF'

      - name: Upload Dependency Check results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: reports/dependency-check-report.sarif

  # Stage 3: Container Security
  container-scan:
    name: Container Security Scan
    runs-on: ubuntu-latest
    needs: [sast, sca]
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: |
          docker build -t ${{ env.IMAGE_NAME }}:${{ github.sha }} .

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Run Grype scanner
        uses: anchore/scan-action@v3
        with:
          image: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
          fail-build: true
          severity-cutoff: high

      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v2
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

  # Stage 4: Infrastructure as Code Scanning
  iac-scan:
    name: IaC Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: ./infrastructure
          framework: terraform,kubernetes,dockerfile
          output_format: sarif

      - name: Run tfsec
        uses: aquasecurity/[email protected]
        with:
          soft_fail: false

  # Stage 5: Dynamic Testing (on staging)
  dast:
    name: Dynamic Application Security Testing
    runs-on: ubuntu-latest
    needs: [container-scan]
    if: github.ref == 'refs/heads/main'
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to staging
        run: |
          # Deploy application to staging environment
          kubectl apply -f k8s/staging/

      - name: Wait for deployment
        run: |
          kubectl rollout status deployment/app -n staging

      - name: Run OWASP ZAP scan
        uses: zaproxy/[email protected]
        with:
          target: ${{ secrets.STAGING_URL }}
          rules_file_name: '.zap/rules.tsv'
          cmd_options: '-a'

      - name: Run Nuclei scanner
        uses: projectdiscovery/nuclei-action@main
        with:
          target: ${{ secrets.STAGING_URL }}
          templates: cves,vulnerabilities,misconfiguration

Container Security

Containers require specific security considerations. Follow these best practices for secure containerization.

📦

Minimal Base Images

Use distroless or alpine images to reduce attack surface

👤

Non-Root User

Never run containers as root user

🔒

Read-Only Filesystem

Mount filesystem as read-only when possible

🛡️

Security Contexts

Drop capabilities, enable seccomp profiles

Secure Dockerfile

# Secure Dockerfile Best Practices
# Use specific version, not 'latest'
FROM python:3.12-slim-bookworm AS builder

# Set build-time arguments
ARG APP_VERSION=1.0.0

# Install build dependencies in builder stage
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
    && pip install --no-cache-dir -r requirements.txt

# Production stage - minimal image
FROM python:3.12-slim-bookworm AS production

# Security: Create non-root user
RUN groupadd --gid 1000 appgroup \
    && useradd --uid 1000 --gid appgroup --shell /bin/false appuser

# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Set working directory
WORKDIR /app

# Copy application code with correct ownership
COPY --chown=appuser:appgroup . .

# Security: Remove unnecessary files
RUN find . -type f -name "*.pyc" -delete \
    && find . -type d -name "__pycache__" -delete \
    && rm -rf .git .env* tests/

# Security: Set restrictive permissions
RUN chmod -R 550 /app

# Environment variables
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONFAULTHANDLER=1 \
    APP_VERSION=${APP_VERSION}

# Security: Switch to non-root user
USER appuser

# Expose port (documentation only)
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

# Use exec form for proper signal handling
ENTRYPOINT ["python"]
CMD ["-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Kubernetes Security Context

# k8s/deployment.yaml - Secure Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  labels:
    app: secure-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
    spec:
      # Security: Use non-root security context
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault

      # Security: Restrict service account
      serviceAccountName: app-service-account
      automountServiceAccountToken: false

      containers:
        - name: app
          image: myapp:v1.0.0@sha256:abc123...  # Use digest
          imagePullPolicy: Always

          # Security: Container-level security context
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
            privileged: false

          # Resource limits prevent DoS
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"

          # Mount secrets securely
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password

          # Writable directories if needed
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /app/.cache

          # Probes for reliability
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 15

          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 5

      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir:
            sizeLimit: 100Mi

      # Security: Network policies
      # Defined separately in NetworkPolicy resource

Security Monitoring

Continuous monitoring enables rapid detection and response to security incidents.

📊

Vulnerability Count

Track open security issues by severity

⏱️

Mean Time to Remediate

Measure how fast vulnerabilities are fixed

Security Test Pass Rate

Percentage of builds passing security gates

🚨

Incident Response Time

Time to detect and respond to threats

Security Metrics Dashboard

Metric Target Alert Threshold
Critical Vulnerabilities 0 Any critical finding
High Vulnerabilities < 5 > 10 open
MTTR (Critical) < 24 hours > 48 hours
MTTR (High) < 7 days > 14 days
Security Test Pass Rate > 95% < 90%
Dependency Update Lag < 30 days > 60 days behind
Failed Login Attempts < 1% > 5% (credential stuffing)

Security Checklists

Pre-Development Checklist
  • Threat model completed for new features
  • Security requirements defined and documented
  • Data classification performed
  • Third-party components evaluated for security
  • Architecture reviewed by security team
Pre-Deployment Checklist
  • All SAST findings addressed (no critical/high)
  • Dependency vulnerabilities resolved
  • Container images scanned and signed
  • Secrets properly managed (no hardcoded values)
  • Security tests pass in CI/CD pipeline
  • Penetration testing completed
  • Security monitoring and alerting configured
Ongoing Security Operations
  • Weekly dependency updates reviewed
  • Monthly access reviews conducted
  • Quarterly penetration testing
  • Security training for new team members
  • Incident response plan tested annually
  • Security champions program active

Incident Response

Every team needs a clear incident response plan. When a security incident occurs, time is critical.

1

Detection & Triage

Identify the incident, assess severity, and classify the type (data breach, malware, unauthorized access, etc.).

2

Containment

Isolate affected systems, revoke compromised credentials, and prevent further damage. Preserve evidence for investigation.

3

Eradication

Remove the threat, patch vulnerabilities, and eliminate attacker access. Verify systems are clean.

4

Recovery

Restore systems to normal operation, monitor for recurrence, and validate security controls are effective.

5

Lessons Learned

Document the incident, conduct post-mortem, identify improvements, and update security controls and procedures.

Key Takeaways

  • Shift left — Integrate security from design, not as an afterthought
  • Automate everything — Manual security checks don't scale; automate in CI/CD
  • Defense in depth — Layer multiple security controls; no single point of failure
  • Least privilege — Grant minimum permissions necessary for each component
  • Assume breach — Design systems to limit blast radius when (not if) a breach occurs
  • Continuous improvement — Security is a journey, not a destination; keep learning

Next Steps

Ready to improve your security posture? Start with these high-impact actions:

1️⃣

Enable SAST/SCA

Add automated scanning to your CI/CD pipeline today

2️⃣

Secrets Audit

Scan repositories for hardcoded secrets and rotate them

3️⃣

Update Dependencies

Address known vulnerabilities in your dependencies

4️⃣

Security Training

Ensure all developers understand secure coding basics

Related Resources