$ yuktics v0.1

T5 — System Design and Scale module 05.4 ~6–10 hrs

Security — the parts you can't skip

OWASP Top 10, auth flows that don't leak, secrets management, threat modelling at the level a non-security engineer should reach.

Prerequisites

  • 03.3

Stack

  • a security-aware web framework (FastAPI/Hono/Next)
  • 1Password or doppler for secrets
  • Burp Suite Community OR OWASP ZAP for testing
  • argon2 / bcrypt for passwords

By the end of this module

  • Walk the OWASP Top 10 and describe a realistic exploit and mitigation for each.
  • Configure session auth that doesn't leak (cookies, CSRF, same-site, rotation).
  • Manage secrets without ever committing one to git.
  • Audit a deployed project against a 25-item checklist in 60 minutes.

You are not going to be a security engineer. This module will not make you one. What it will make you is the developer in the room who doesn’t ship the embarrassing vulnerability — the one who knows what SSRF is, can spot a missing CSRF token, doesn’t store passwords with sha256, and can sketch a basic threat model without panicking. That is the bar. It is achievable in a weekend if you’re disciplined.

The opinion: the biggest security wins for an individual developer are configuration choices, not clever code. Strict same-site cookies, argon2 password hashing, parameterized queries, secrets in a vault, dependency updates, HTTPS with HSTS — these are checkboxes that prevent 80% of real-world breaches. The novel exploits, the fancy chained CVEs, the nation-state attacks: those are someone else’s problem at your scale. Get the boring stuff right and you’re already ahead of the median deployed app.

Set up

mkdir security && cd security
uv venv .venv && source .venv/bin/activate
uv pip install fastapi uvicorn argon2-cffi python-jose passlib python-multipart \
  httpx itsdangerous bandit safety

# Security testing tools
brew install --cask owasp-zap   # OR install Burp Suite Community
brew install gitleaks

You need a deployed project to audit. Use any project you’ve shipped — your 03.7 backend, an old hackathon site, anything live. The point is to find the real holes in your real code.

Read these first

Three sources, in order, then stop:

  1. OWASP — Top 10: 2021. docs · 1 hr · the canonical list. Each item has a real-world example and mitigation. Read every item, not just the famous ones.
  2. Mozilla — Web Security Cheat Sheet. docs · 30 min · pragmatic, well-scoped, actionable. Mozilla’s security team is unusually good at writing for non-security devs.
  3. Adam Shostack — Threat Modeling: Designing for Security, chapter 1. book · 30 min · STRIDE in plain language. The rest of the book is good but not strictly necessary.

Skip “Top 10 Hacking Tools” content. It is roughly as useful for security as “Top 10 IDEs” is for engineering.

OWASP Top 10, walked through

These are the categories that cause the most real-world breaches. Memorize them. The categories blur and overlap; that’s fine. Recognition is the goal.

#CategoryRealistic exampleMitigation
1Broken Access ControlUser A reads /orders/123 belonging to User BCheck ownership server-side on every request
2Cryptographic FailuresPassword stored with sha256 (no salt)argon2id with proper params
3InjectionString-concat SQL like "SELECT * FROM users WHERE name = '" + name + "'"Parameterized queries, ORMs by default
4Insecure Design”Forgot password” with a 4-digit code, no rate limitThreat-model auth flows before building
5Security MisconfigurationDebug endpoints exposed in prod, default credsHardened defaults, environment-aware config
6Vulnerable ComponentsjQuery 1.7 still in package.jsonRenovate/Dependabot + monitor advisories
7Authentication FailuresSession ID in URL, no rotation on loginStrong session tokens, rotate on auth events
8Software/Data IntegrityImporting untrusted JS via CDN without SRISRI tags, signed packages, verified registries
9Logging/Monitoring FailuresFailed login spike unnoticed for weeksAlerts on auth anomalies, store login attempts
10SSRF (Server-Side Request Forgery)?url=http://169.254.169.254/... to read AWS metadataAllowlist, block private/link-local IPs

Walk through each one against your own project right now. If you can’t honestly say “I’m fine on category N,” that’s where to start fixing.

CSRF and same-site cookies

Cross-Site Request Forgery: a malicious site triggers an authenticated request from your logged-in user to your server. For a long time this required CSRF tokens on every form. Then browsers added the SameSite cookie attribute, and most CSRF went away.

from fastapi import Response

response.set_cookie(
    key="session",
    value=token,
    httponly=True,        # JavaScript can't read it (XSS protection)
    secure=True,          # HTTPS only
    samesite="strict",    # Don't send on cross-site requests
    max_age=86400,
)

SameSite=strict blocks most CSRF. SameSite=lax (the default in modern browsers) blocks most CSRF except top-level GET navigations, which is usually fine. Either way: still add explicit CSRF tokens on state-changing forms if you’re paranoid or supporting legacy browsers.

Secrets management

Rule one: never commit secrets to git. Rule two: assume rule one will be violated by you or a teammate at some point, and design accordingly.

WhereUse it for
.env (local only, in .gitignore)Local dev keys with limited scope
1Password / Doppler / InfisicalTeam-shared secrets, dev and prod
AWS Secrets Manager / Google Secret ManagerProduction secrets accessed by services
Hashicorp VaultLarge orgs with rotation requirements

Never use:

  • A .env file checked into git (huge percentage of GitHub leaks).
  • Secrets in CI variable strings without scoping (they leak in build logs).
  • Email or Slack to share API keys.

Add gitleaks as a pre-commit hook. It scans for accidentally-committed secrets and refuses the commit:

brew install gitleaks
cd your-repo
cat > .pre-commit-config.yaml <<'EOF'
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks
EOF
pre-commit install

If a secret leaks: rotate immediately. Treat it as compromised even if “no one saw it.” The git history is forever.

Password storage

Three rules, in order of importance:

  1. Use argon2id. It’s the modern winner. bcrypt is acceptable. Anything else (sha256, md5, plain) is a vulnerability.
  2. Tune memory and iterations. Default library settings are usually fine; verify they take ~250ms per hash on your server.
  3. Never store passwords in plaintext, never log them, never email them.
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,
    memory_cost=64 * 1024,   # 64 MB
    parallelism=4,
)

# Hash on registration
hash = ph.hash(plaintext_password)

# Verify on login
try:
    ph.verify(hash, plaintext_password)
    # Optionally re-hash if parameters changed
    if ph.check_needs_rehash(hash):
        new_hash = ph.hash(plaintext_password)
        # update DB
except argon2.exceptions.VerifyMismatchError:
    # invalid password
    pass

”Do NOT roll your own crypto”

This is rule zero. If you find yourself implementing AES, RSA, JWT signing, password hashing, or random ID generation by hand, stop. Use the standard library. Use a vetted library.

Specifics:

NeedUse thisDon’t use this
Random tokenssecrets.token_urlsafe(32)random (predictable)
Password hashingargon2-cffi, passlib[bcrypt]sha256, md5
Signed cookiesitsdangerous, framework session middlewarehand-rolled HMAC
JWTpython-jose or framework authhand-rolled
TLSreverse proxy (nginx, Caddy, Cloudflare)Python’s ssl raw
Symmetric encryptioncryptography.fernethand-rolled AES

SSRF — the cloud-era vulnerability

If your server fetches a URL the user supplied (image proxy, webhook delivery, URL preview), the user can ask it to fetch internal addresses:

  • http://169.254.169.254/latest/meta-data/ (AWS metadata service — leaks instance credentials)
  • http://localhost:6379/ (Redis on the same box)
  • http://10.0.0.5:8080/admin (internal services)

Allowlist domains if you can. Otherwise, resolve the URL and reject private/link-local IPs before the actual fetch. Re-resolve after redirects (DNS rebinding is real). Use a library like requests-doh or the SSRF protections in httpx if available.

import ipaddress, socket
from urllib.parse import urlparse

def is_safe_url(url):
    parsed = urlparse(url)
    try:
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
    except (ValueError, socket.gaierror):
        return False
    if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
        return False
    return True

This is a starting point, not a complete defense. If SSRF is a real risk in your app, use a hardened HTTP client library that handles redirects and rebinding properly.

Session management

Three rules.

  1. Generate sessions with secrets.token_urlsafe(32) or equivalent. 256+ bits of entropy.
  2. Rotate on auth events: login, logout, privilege change, password reset.
  3. Set sane lifetimes. 30 days for “remember me,” 24 hours for sensitive sessions, idle timeouts on banking-class apps.

Stateless JWT sessions tempt because they avoid a session table. Don’t do them for browser sessions unless you have a real reason — you lose the ability to revoke instantly. Server-side session tokens (random ID, looked up against a session table) are simpler, safer, and faster than people think.

HTTPS, HSTS, CSP

Three headers you should set on any web app, in production:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM'; ...
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), geolocation=()

Use Mozilla’s Observatory or securityheaders.com to score your site. Aim for an A. The B from “X-Frame-Options missing” is a 30-second fix.

CSP in particular is hard to get right (especially with third-party scripts). Use a report-only mode first, see what would have been blocked, then enforce.

Dependency hygiene

Outdated dependencies are the boring vector for a huge fraction of real breaches. Two things:

  1. Enable Dependabot or Renovate on your repo. Update dependencies weekly, not yearly.
  2. Subscribe to security advisories for your stack: GitHub Security Advisories, npm audit, pip-audit, RustSec, etc.

Run safety check (Python) or npm audit (Node) in CI. Don’t merge PRs that introduce new high-severity issues.

The 60-minute audit

Set a timer. Audit your own deployed project against this checklist:

[ ] HTTPS enforced (HTTP redirects to HTTPS)
[ ] HSTS header set with max-age >= 6 months
[ ] Cookies are HttpOnly, Secure, SameSite=lax or strict
[ ] CSP header set, even if just default-src 'self'
[ ] X-Content-Type-Options: nosniff
[ ] X-Frame-Options: DENY (or CSP frame-ancestors equivalent)
[ ] Passwords stored with argon2 or bcrypt, never sha256/md5
[ ] Session tokens are 256+ bits, rotated on login
[ ] Rate limiting on login, password reset, signup
[ ] All SQL is parameterized (no string concat)
[ ] All user-rendered HTML is escaped (or sanitized if HTML allowed)
[ ] CSRF tokens on state-changing forms (or SameSite=strict)
[ ] No secrets in git history (gitleaks scan)
[ ] No secrets in CI logs
[ ] No secrets in client-side JS bundles
[ ] Dependabot enabled, recent updates merged
[ ] No high-severity advisories open
[ ] Auth required on every endpoint that needs it (check by hand, not by middleware)
[ ] User-supplied URLs are validated before server-side fetch (SSRF)
[ ] File upload type/size validated; uploaded files served from separate origin
[ ] Error messages don't leak stack traces or DB errors to users
[ ] Failed-login attempts logged and alerted on spikes
[ ] Backups exist and have been restored at least once
[ ] An incident response plan exists, even if it's a one-pager
[ ] You can rotate any secret in under 1 hour

If any item is “no” — fix it before continuing. Most are sub-30-minute fixes.

Going deeper

When you have specific questions, in this order:

  1. OWASP — Cheat Sheet Series. docs — task-specific, dense, well-maintained. Bookmark.
  2. PortSwigger Web Security Academy. academy — free, interactive, the best hands-on intro to actually exploiting things you should defend against.
  3. Adam Shostack — Threat Modeling. book — read the rest after the chapter you read above. STRIDE is the framework most teams adopt.
  4. Cure53 / Trail of Bits — published audit reports. trailofbits — what real auditors look at and find. Excellent calibration for what “thorough” means.

Skip “Hack The Box” if your goal is shipping secure code. It’s a rabbit hole optimized for offense, not defense, and most of the skills don’t transfer.

Checkpoints

If any wobbles, reread the corresponding section.

  1. Walk through the difference between SameSite=strict and SameSite=lax cookies. When does each break a real user flow?
  2. You discover a teammate stored passwords with sha256 plus a per-user salt. Why is that still inadequate? What’s the migration plan?
  3. Sketch an SSRF attack against a “URL preview” feature. What’s the defense, end to end?
  4. Your CSP is default-src 'self'. A product manager asks to embed a YouTube video. What changes, and what’s the risk?
  5. Run the 60-minute audit on a deployed project. How many items fail? Pick the worst three and write a 2-week plan to fix them.

When you can answer all five from memory, move to 06.1 Side projects that compound. The system you’ve built across T5 is now a real engineering surface — security is what makes it shippable, not just functional.