MODULE 1

Threat Modeling

Structured analysis of who could attack, how, and what to protect.

STRIDE

Microsoft's 6-category threat taxonomy. Walk every component, asset, and trust boundary through each category.

LetterThreatDefense property
SSpoofing identityAuthentication
TTampering with dataIntegrity
RRepudiationNon-repudiation (signed audit log)
IInformation disclosureConfidentiality
DDenial of serviceAvailability
EElevation of privilegeAuthorization
STRIDE × component matrix

Data Flow Diagrams & Trust Boundaries

Draw external entities, processes, data stores, data flows. Trust boundaries cross where credentials change (browser→server, service→DB, public→private VPC). Every boundary crossing gets a STRIDE pass.

Simple web-app threat model (data flow + trust boundaries)

Attack Trees

Root = attacker's goal. Children = necessary conditions (AND/OR). Leaves = concrete attacks. Use to identify the cheapest path for the attacker and harden that first.

Attack tree: steal admin credentials
MODULE 2

Cryptography Essentials

What to use, when. Never roll your own.

Symmetric Encryption

Same key encrypts and decrypts. Use for data at rest and any large payload. Modern pick: AES-256-GCM or ChaCha20-Poly1305 — both are AEAD (authenticated encryption with associated data), so they encrypt AND integrity-check.

# Python: correct AES-GCM with random nonce
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aes = AESGCM(key)
nonce = os.urandom(12)  # 96-bit nonce — NEVER reuse with same key
ct = aes.encrypt(nonce, b"secret", associated_data=b"context-v1")
pt = aes.decrypt(nonce, ct, b"context-v1")

Asymmetric (Public-Key)

Different keys for encrypt/decrypt or sign/verify. Slow → use for key exchange and signatures, not bulk data.

  • RSA-2048+ — widely supported, slow. Avoid for new code.
  • ECDSA P-256 — fast, standard in TLS / JWT / code signing.
  • Ed25519 — fastest, simpler impl, deterministic signatures, preferred for new code.
  • X25519 — key agreement (Diffie-Hellman) counterpart to Ed25519.

Hash Functions

One-way, fixed-length digest. Use cases:

  • Integrity: SHA-256 / SHA-3-256 / BLAKE3 — any modern cryptographic hash.
  • Password storage: use a KDF, not a plain hash. See below.
  • Content addressing: BLAKE3 for speed; SHA-256 for ecosystem compat.

Key Derivation & Password Hashing

Passwords are low-entropy — a plain SHA-256 falls to GPU brute-force. Use a slow, memory-hard KDF.

  • Argon2id — modern winner of PHC. Default for new systems. Parameters: m=64 MB, t=3, p=1 is a safe baseline.
  • scrypt — memory-hard, well supported.
  • bcrypt — cost 12+. Capped at 72-char password (truncates longer).
  • PBKDF2-HMAC-SHA-256 — fallback if FIPS required. iter ≥ 600k (2023 OWASP).
# Python: Argon2id
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=64*1024, parallelism=1)
hashed = ph.hash("user-password")        # store this
ph.verify(hashed, "user-password")       # raises on mismatch
if ph.check_needs_rehash(hashed):        # after param bump
    hashed = ph.hash("user-password")

Slow-on-purpose: attacker with GPU farm sees O(1) hashes/sec; legitimate login path is fine.

Password verification flow (Argon2id)

HMAC & Signatures

HMAC = keyed-hash MAC. Use for integrity + authenticity when both sides share a symmetric secret (webhooks, cookies, JWT HS256). Never use plain hash(secret + data) — length-extension attacks.

Digital signatures = asymmetric HMAC. Signer holds private key, verifiers hold public. Ed25519 / ECDSA for new code.

# Webhook signature verification (Python)
import hmac, hashlib
def verify(body: bytes, header_sig: str, shared_secret: bytes) -> bool:
    expected = hmac.new(shared_secret, body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header_sig)  # constant-time!
MODULE 3

Authentication

Who is the caller? Passwords, MFA, tokens, federation.

Passwords Done Right

  • Never store plaintext. Never log passwords. Never include in error messages.
  • Store argon2id(password, salt, params). Salt is stored inside the hash string — library handles it.
  • Enforce NIST 800-63B minimums: ≥ 8 chars, no composition rules, reject known-breached (HIBP k-anonymity API).
  • Rate-limit per-IP and per-account. Lock or MFA-challenge after N fails.
  • Forgot-password: time-limited single-use token emailed; token hashed in DB.

Multi-Factor Auth

  • TOTP (RFC 6238) — 6-digit code from shared seed, 30-s window. Works offline. Prefer over SMS.
  • WebAuthn / FIDO2 — hardware or platform authenticators (Touch ID, Windows Hello, YubiKey). Phishing-resistant because origin-bound.
  • SMS — vulnerable to SIM swap. Only for recovery / low-risk, never primary for admin.

Sessions vs Stateless Tokens

Sessions: random opaque ID in cookie, server looks up state in Redis/DB. Pros: instant revoke. Cons: stateful, sticky needed.

Stateless JWT: signed claims. Pros: scale-out, no DB per request. Cons: hard to revoke before expiry.

Practical: short-lived access JWT (~15 min) + opaque refresh token stored server-side. Revoke refresh → access expires quickly.

JWT Pitfalls

  • alg: "none" — library MUST reject. Historical CVE.
  • Algorithm confusion — server expects RS256 but attacker sends HS256 signed with the server's public key treated as secret. Pin algorithm; don't trust header alg.
  • Weak HS256 secret — brute-forceable. Use 256-bit random or switch to RS256/Ed25519.
  • Long TTL — hours/days without refresh is bad. Access ≤ 15m.
  • Storing in localStorage — accessible to any XSS. Prefer HttpOnly cookie with SameSite=Lax.
  • No audience / issuer check — token from service A accepted by service B.
JWT structure
# Correct JWT verify (Python, PyJWT)
import jwt
payload = jwt.decode(
    token, public_key,
    algorithms=["RS256"],      # pin — not trust header
    audience="api.example.com",
    issuer="https://idp.example.com",
    options={"require": ["exp", "iat", "sub", "aud", "iss"]},
)

OAuth 2.0 + PKCE

OAuth delegates authorization. Auth-code + PKCE is the right flow for all public clients (SPA, native, mobile). Client credentials for machine-to-machine.

OAuth 2.0 Authorization Code + PKCE

OIDC & SAML

OIDC = OAuth + id_token (JWT with user claims). Modern federation default.

SAML 2.0 = XML-based SSO, still dominant in enterprise. More complex; beware XML signature wrapping attacks. Prefer OIDC for new deployments.

MODULE 4

Authorization

What is the caller allowed to do?

RBAC

Users → Roles → Permissions. Simple, widely supported, doesn't express resource-level rules well ("Alice can edit only her docs").

ABAC

Attribute-based. Policy = function of (subject attrs, resource attrs, action, environment). Expressive; needs a policy engine. Standards: XACML (complex), Rego (OPA), Cedar (AWS).

# OPA Rego — allow doc edit if owner OR admin
package docs
default allow = false
allow {
  input.action == "edit"
  input.resource.owner == input.subject.id
}
allow {
  input.subject.roles[_] == "admin"
}

ReBAC (Google Zanzibar-style)

Relationship-based — who has what relation to what. Scales to billions of tuples. Pattern: tuples (user, relation, object) with indirection via usersets ("user:alice is in group:eng"). Used by Google Drive, GitHub, Figma.

ReBAC check: can alice edit doc42?

Least Privilege

  • Default deny; explicit allow.
  • Scope tokens narrowly (per-service audience, per-action scope).
  • Rotate service credentials; prefer workload identity (AWS IRSA, GCP WIF) over long-lived keys.
  • Separate read vs write keys/roles.
MODULE 5

Web Vulnerabilities — OWASP Top 10 Defense

Concepts + correct defenses. Defense-only (no exploit recipes).

A03 Injection — SQL / NoSQL / Command

Cause: concatenating untrusted input into a query. Defense: parameterized statements. Never build a query via string formatting.

# Python — parameterized (safe)
cur.execute("SELECT * FROM users WHERE email = %s AND active = %s", (email, True))

# NEVER this:
# cur.execute(f"SELECT * FROM users WHERE email = '{email}'")

For NoSQL (MongoDB), reject operator objects in user input: validate shape with a schema (pydantic, joi). For shell commands, use subprocess.run([...]) with a list, never shell=True.

A03 XSS — Cross-Site Scripting

  • Reflected — input echoed in response. Defense: context-aware output encoding.
  • Stored — input saved then rendered for others. Same defense, at render time.
  • DOM-based — client JS writes untrusted data to sink (innerHTML, document.write).

Defense stack (all layers):

  • HTML-encode on output. Use framework auto-escape (React's JSX, Jinja2 autoescape, Handlebars).
  • Never inject untrusted data into a script, style, onclick, or href=javascript:. If needed, use Trusted Types (MDN).
  • CSP (Content-Security-Policy) header with default-src 'self'; script-src 'nonce-xxx' 'strict-dynamic'. Blocks inline <script> and eval.
  • HttpOnly + Secure + SameSite=Lax on session cookies → stolen DOM can't reach them.

A01 CSRF — Cross-Site Request Forgery

Attacker's site triggers a state-changing request to yours using the victim's cookies. Defense:

  • SameSite=Lax cookies (default in modern browsers) block most cross-site POSTs.
  • CSRF token: synchronizer-token pattern — server issues a random token, client echoes in a custom header; server compares constant-time.
  • Check Origin / Referer header as a second layer.
  • For pure-JSON APIs using Bearer tokens (no cookies), CSRF is not an issue.

A10 SSRF — Server-Side Request Forgery

User input becomes a URL the server fetches. Attacker targets 169.254.169.254 (cloud metadata) or internal services. Defense:

  • Allow-list of host/port/scheme.
  • Resolve DNS yourself, reject private-range IPs (RFC1918 + link-local + loopback). Beware TOCTOU — re-resolve and pin.
  • Disable redirects OR re-check each hop.
  • Prefer a dedicated egress proxy with metadata-service blocked.
  • On AWS: enforce IMDSv2 (session-token required) so a blind GET can't read creds.

A01 IDOR — Insecure Direct Object Reference

Endpoint trusts an object ID from the request without authz check. Defense: every object fetch must check the caller owns/has-access to the object. Don't rely on "UUID is random enough" — that's obscurity, not security.

# FastAPI: correct ownership check
@app.get("/doc/{doc_id}")
def get_doc(doc_id: str, user = Depends(current_user)):
    doc = db.get(doc_id)
    if doc is None or not can_read(user, doc):
        raise HTTPException(404)  # don't leak existence
    return doc

A08 Insecure Deserialization

pickle.loads, yaml.load, Java ObjectInputStream on untrusted input = arbitrary code exec. Use JSON with a strict schema (pydantic, jsonschema). If you must use pickle, sign payloads with HMAC and verify before loading.

Other OWASP Hits

  • A02 Crypto Failures — weak TLS config, hard-coded keys. Use Mozilla SSL Config Generator.
  • A04 Insecure Design — threat-model at design time (mod1).
  • A05 Misconfig — defaults (default creds, open S3 bucket, open CORS). Scan with checkov / tfsec.
  • A06 Vuln Components — dep scanning: npm audit, pip-audit, Snyk, Dependabot.
  • A07 Auth Failures — see mod3.
  • A09 Logging & Monitoring — structured audit log; can you reconstruct who did what to whom, yesterday?
MODULE 6

Transport Security

TLS 1.3, mTLS, HSTS, cert pinning.

TLS 1.3

1-RTT handshake (0-RTT with PSK). Only AEAD ciphers. Drops RSA key exchange, SHA-1, CBC. See networking mod6 for the handshake timeline.

  • Pin cipher suites: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256.
  • ECDHE for forward secrecy (baked-in in 1.3).
  • Disable old protocols: SSLv2/3, TLS 1.0, TLS 1.1.

mTLS

Both sides present certs. Used service-to-service in mesh (Istio, Linkerd). Identity = SPIFFE/SPIRE IDs embedded in cert SAN.

HSTS + HTTPS Enforcement

# Strict-Transport-Security header
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Tells browser: only HTTPS for this host for 1 year. Submit to hstspreload.org to bake into browser source — no first-request window.

Cert Pinning & CT

Pinning = app hard-codes accepted cert or public-key hash. Kills MITM via rogue CA but brittle on rotation. Mobile apps common, web is deprecated (HPKP dead).

Certificate Transparency = public append-only log of issued certs. Monitor for unauthorized certs on your domain (crt.sh, Cert Spotter).

TLS certificate chain
MODULE 7

Secrets Management

Keep secrets out of code, config, and logs.

Never Commit Secrets

  • Pre-commit hook: gitleaks / detect-secrets.
  • Rotate anything ever committed — even if rolled back, it's in history.
  • .env files: gitignored, but still unencrypted on disk. OK for dev, NOT prod.

Secret Managers

  • HashiCorp Vault — dynamic secrets, leases, policies.
  • AWS Secrets Manager / Parameter Store, GCP Secret Manager, Azure Key Vault.
  • Kubernetes: external-secrets operator syncs from a manager into K8s Secret.

Envelope Encryption

Encrypt data with a DEK (data encryption key). Encrypt the DEK with a KEK held by KMS. Store {ciphertext, wrapped_DEK}. Rotation: re-wrap DEK by KEK rotation — don't re-encrypt data.

Envelope encryption

Rotation

  • All credentials have a max age. Short-lived tokens via OIDC federation (AWS IRSA, GCP WIF) > long-lived access keys.
  • Design for overlap: new secret valid before old retired.
  • Alert on un-rotated secrets past threshold.
MODULE 8

Secure Coding Patterns

Defaults that prevent whole classes of bugs.

Validate Input, Encode Output

Validation is allow-list (positive pattern). Encoding is context-aware: HTML body ≠ HTML attr ≠ URL ≠ JSON ≠ script. Use the framework's escaper for the target context.

Rate Limiting

Token bucket per (IP, account, endpoint). Prevents brute-force + abuse. See system-design mod9 for token-bucket viz.

Dependency Scanning

  • SCA (Software Composition Analysis) — match lockfiles against CVE DB. Snyk, GitHub Dependabot, npm audit, pip-audit, OSV-scanner.
  • SAST — static analysis on your source. Semgrep, CodeQL.
  • DAST — probe the running app. ZAP, Burp.
  • SBOM — Software Bill of Materials in SPDX/CycloneDX format.

Safe Deserialization

JSON only, with a schema. Never pickle.loads or yaml.load on untrusted data. YAML: use yaml.safe_load.

Logging Hygiene

  • Never log: passwords, tokens, full PAN, full SSN, keys, session cookies.
  • Mask or hash sensitive fields. Include request ID for trace correlation.
  • Structured JSON logs; centralize in SIEM.
MODULE 9

Network & Zero Trust

Don't trust the network — authenticate every request.

Beyond Perimeter

Legacy model: hard shell (firewall) around soft interior. Breaks once attacker is inside. Google's BeyondCorp model: every request authenticated + authorized regardless of origin network.

Zero Trust Primitives

  • Identity-aware proxy (IAP) between client and app. Terminates TLS, authenticates user via OIDC, forwards identity headers to backend.
  • Service mesh mTLS (Istio, Linkerd) — every intra-cluster call authenticated + encrypted by the data-plane proxy.
  • Workload identity — SPIFFE SVIDs for services; no shared secrets.
  • Egress control — explicit allow-list of external domains a workload may reach.
BeyondCorp-style request flow

Network Segmentation

Even in zero-trust, minimize blast radius: separate VPCs per environment (prod/stage/dev), private subnets for data stores, PrivateLink for cross-account. Public subnet only for LB/NAT.

MODULE 10

Interview Cheat Sheet

Questions you WILL be asked.

"How does HTTPS work?"

Client hello → server hello + cert chain → client verifies chain against trust store → ECDHE key agreement → derive session keys → AEAD cipher (TLS 1.3 = 1-RTT). See networking mod6.

"Design an auth system."

Identify users, workloads, and federation. Argon2id for passwords + WebAuthn for MFA. Short-lived access JWT (RS256, 15 min) + opaque refresh token stored server-side. OAuth PKCE for public clients. Session revocation via refresh-token blacklist or JWT + short TTL.

"How to store passwords?"

Argon2id(m=64MB, t=3, p=1) — library stores salt inside hash. Never plain hash. Rate-limit login. Check against HIBP on set.

"What's in a JWT? Risks?"

Header (alg, kid) + payload (iss, sub, aud, exp, iat, custom claims) + signature, base64url-joined. Risks: alg=none, algorithm confusion, weak HS256 secret, long TTL, storing in localStorage.

"Prevent SQL injection, XSS, CSRF, SSRF."

Parameterized queries · context-aware output encoding + CSP · SameSite cookies + CSRF token · allow-list + re-resolve DNS + block private IPs + IMDSv2.

"Secret management for a service on K8s?"

External-secrets operator → fetches from Vault/Secrets Manager → K8s Secret → projected as env/volume. Pod uses IRSA/WIF for manager auth — no shared keys. Rotate automatically.

"How do you design a rate limiter?"

Token bucket per (key). Redis with INCR + EXPIRE, or Envoy local rate limit + global. Burst = bucket size; steady = refill rate. See system-design mod9.

"Encrypt data at rest — envelope encryption why?"

Symmetric DEK fast for bulk; KEK in KMS never leaves the boundary. Rotation = re-wrap DEK, no data re-encryption. Cheap audit: KMS logs every unwrap.