PART V (j) — Fullstack Security for Seniors (Threat Modeling, OWASP-by-Pattern)
SECURITY IS A SYSTEM PROPERTY
Senior engineers don't "add security."
They design so insecure states are hard to reach.
Defense in depth: No single control is perfect. Combine layers: input validation + parameterized queries + least privilege + audit. One layer fails; others may catch it.
THREAT MODELING (LIGHTWEIGHT, REPEATABLE)
For any feature, answer:
-
what are the assets? (accounts, money, data)
-
who are the attackers? (anon, user, insider)
-
what are the entry points? (API, UI, webhooks, uploads)
-
what's the worst-case impact?
Output:
-
top 5 threats
-
mitigations
-
residual risk
STRIDE framework:
| Threat | Question | Example |
|---|---|---|
| Spoofing | Can someone pretend to be someone else? | Session fixation, token theft |
| Tampering | Can data be modified in transit or at rest? | XSS, SQL injection |
| Repudiation | Can someone deny an action? | Missing audit logs |
| Information disclosure | Can sensitive data leak? | Logs, error messages, IDOR |
| Denial of service | Can the system be made unavailable? | Rate limits, resource exhaustion |
| Elevation of privilege | Can a user gain more access? | Broken access control, IDOR |
Concrete threat model example: File upload feature
| Asset | Entry point | Threat | Mitigation |
|---|---|---|---|
| User storage | POST /upload | Malicious file (virus, polyglot) | Magic bytes validation, virus scan |
| Server disk | Upload path | Path traversal (../../../etc/passwd) | Sanitize filename, store in isolated dir |
| Other users | Download URL | IDOR to access others' files | AuthZ check: file.owner_id == request.user.id |
| Application | Stored filename | XSS via filename in UI | Encode output; don't use filename as HTML |
| Availability | Upload endpoint | DoS via huge files | Size limit, rate limit per user |
Threat model process: 1) List assets; 2) List attackers (external, insider, malicious user); 3) List entry points; 4) For each asset+entry, ask STRIDE questions; 5) Rank by likelihood × impact; 6) Document top 5–10 mitigations. Revisit when feature changes.
Senior rule:
Threat model before you build. Fixing design flaws after ship is 10x harder.
OWASP BY PATTERN (WHAT SENIORS ACTUALLY APPLY)
OWASP Top 10 changes over time. Seniors focus on patterns that apply across versions: injection, broken auth, XSS, CSRF, sensitive data exposure, broken access control.
XSS
-
output encoding
-
avoid dangerouslySetInnerHTML
-
CSP
-
keep secrets out of JS (HttpOnly cookies)
Context-aware encoding: HTML context: escape <>&". Attribute context: escape quotes. JavaScript context: escape \ and quotes. URL context: encode. Use a library (DOMPurify, etc.); don't hand-roll.
CSP headers (Content-Security-Policy):
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline'; /* tighten if possible */
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
default-src 'self'— only load from same origin by default.script-src— restrict where scripts run. Avoid'unsafe-inline'unless required.frame-ancestors 'none'— prevent clickjacking.
CSRF
-
SameSite cookies
-
CSRF tokens for unsafe methods
-
Origin/Referer checks
Cookie attributes table:
| Attribute | Purpose | Recommendation |
|---|---|---|
| HttpOnly | No JS access | Always for session/auth cookies |
| Secure | HTTPS only | Always in production |
| SameSite | CSRF protection | Strict or Lax |
| Path | Scope | Narrow as possible |
| Max-Age | Expiry | Short for session; longer for refresh |
CSRF token implementation: Generate token per session; include in form or meta tag. Validate on POST/PUT/DELETE. Token should be random, unpredictable. Store server-side or in signed cookie. Double-submit cookie: store token in cookie; send same value in header; server compares.
CORS
-
explicit allow-list
-
never with credentials
CORS configuration: Set Access-Control-Allow-Origin to specific origins, never * when credentials are used. Access-Control-Allow-Credentials: true + * is invalid. Preflight (OPTIONS) must return correct headers; cache preflight for 86400s to reduce requests.
SSRF
-
avoid server-side fetch of user-provided URLs
-
egress allow-list + DNS/IP protections
SSRF defense layers:
- Allowlist: Only fetch URLs from known domains (e.g., webhook URLs you configure).
- Blocklist: Reject localhost, 127.0.0.1, 10.x, 169.254.x, metadata URLs.
- DNS rebinding protection: Resolve DNS once; use the resolved IP for the request; don't trust Host header.
- Network segmentation: Run fetchers in a sandbox with restricted egress.
Injection
-
parameterized queries
-
validate inputs
NoSQL injection: Same principle. Never concatenate user input into queries. Use parameterized API: db.collection.find({ id: userInput }) not db.collection.find({ $where: "this.id == '" + userInput + "'" }).
SQL injection — parameterization example:
# BAD
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# GOOD
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# GOOD (ORM)
User.objects.get(id=user_id)
Secrets / supply chain
-
don't commit secrets
-
dependency scanning
-
minimal permissions
Input validation: Validate type, length, format. Reject invalid early. Use allowlists (e.g., enum of allowed values) over blocklists. Never trust client-side validation alone.
Command injection: If you shell out (exec, system, child_process), never pass user input directly. Use parameterized APIs. Sanitize or avoid shell metacharacters. Prefer library APIs over shell commands.
SECURE-BY-DEFAULT API/UI PATTERNS
-
cookie vs token: prefer cookie (HttpOnly) for web refresh tokens
-
short-lived access tokens
-
least privilege scopes
-
audit logs for privileged actions
-
rate limits on auth + abuse endpoints
Token lifecycle diagram:
┌─────────┐ ┌─────────────┐ ┌──────────┐
│ Client │ Login (creds) │ Auth │ Issue tokens │ API │
└────┬────┘ ──────────────────► └──────┬──────┘ ──────────────────► └─────────┘
│ │
│ Access token (short, 15m) │ Refresh token (long, 7d)
│ in memory / Authorization │ stored HttpOnly cookie
│ │
│ API request + Bearer token │
│ ─────────────────────────────────────────────────────────► 200 OK
│ │
│ 401 Expired │
│ ◄─────────────────────────────────────────────────────────
│ │
│ POST /refresh (cookie only) │
│ ─────────────────────────► │ New access token
│ ◄───────────────────────── │
Session management patterns:
- Rotate session ID on login (prevent session fixation).
- Invalidate all sessions on password change.
- Absolute logout: delete server-side session; clear cookies.
- Idle timeout: expire session after N minutes of inactivity.
Permission model design:
| Model | Use | When |
|---|---|---|
| RBAC (Role-Based) | User has role; role has permissions | "Admin can delete users" |
| ABAC (Attribute-Based) | Policy evaluates attributes | "User can edit doc if owner or shared" |
Senior rule:
Prefer RBAC for simple apps. Use ABAC when permissions depend on resource attributes (e.g., "owner of this document").
COMMON FLOWS: WHAT TO CHECK
-
Auth: session fixation, token replay, refresh storms
-
Uploads: validate magic bytes, scan before serving, signed URLs
-
Webhooks: signature verify, replay protect, idempotent handlers
-
Admin tools: strict authZ, strong auditing
Auth flow checks:
- Session fixation: Issue new session ID on login; never reuse pre-auth ID.
- Token replay: Short-lived access tokens; refresh token rotation.
- Refresh storms: Rate limit refresh endpoint; detect and block abuse.
Upload flow checks:
- Magic bytes: Validate file signature (JPEG starts with FF D8 FF), not just extension.
- Scan before serving: Virus scan before making file available.
- Signed URLs: For private storage, use time-limited signed URLs; never expose raw paths.
OAuth2/OIDC flow security:
- Use PKCE for public clients (SPAs, mobile).
- Validate state parameter to prevent CSRF.
- Verify redirect_uri against allowlist.
- Store tokens securely; never in URL or localStorage for refresh tokens.
Webhook flow checks:
- Signature verify: HMAC of body with shared secret. Reject invalid.
- Replay protect: Reject duplicate delivery IDs or timestamps outside window.
- Idempotent handlers: Same webhook delivered twice → same result.
Admin tool checks: Require MFA for admin actions. Log every admin action with actor, timestamp, resource. Restrict admin UI by IP or VPN. Use separate admin accounts, not elevated user accounts.
SUPPLY CHAIN SECURITY
Why it matters: Log4j, event-stream, ua-parser-js — supply chain attacks are real. A single compromised dependency can affect thousands of apps. Seniors treat dependencies as untrusted until verified.
Dependency scanning:
- Snyk, npm audit, Dependabot: Run in CI. Fail build on critical vulns.
- Lockfiles: Commit package-lock.json, yarn.lock. Reproducible installs.
- Pinning: Pin major versions; review minor/patch updates.
Lockfile integrity:
- Use
npm cioryarn install --frozen-lockfilein CI. - Reject any install that would modify lockfile.
SBOMs (Software Bill of Materials):
- Generate SBOM (e.g., CycloneDX, SPDX) for built artifacts.
- Enables vulnerability scanning of transitive deps.
CI pipeline security:
- Use least-privilege tokens for CI.
- Sign commits; verify in CI.
- Don't run secrets in logs; use secret masking.
Dependency update strategy: Patch critical vulns immediately. Minor updates: batch weekly. Major updates: plan and test; may have breaking changes. Use npm audit fix or equivalent; review before merging.
Provenance and attestation: Sign artifacts (e.g., Sigstore). Verify artifact signatures before deploy. Supply chain Levels for Software Artifacts (SLSA) defines maturity levels.
Minimal dependency principle: Prefer fewer dependencies. Each new package is a new attack surface. Ask: "Do we really need this? Can we implement a minimal version ourselves?"
Senior rule:
A compromised dependency is a compromised app. Scan and lock everything.
SECURITY HEADERS & TRANSPORT
Headers to set:
| Header | Purpose | Example |
|---|---|---|
| Strict-Transport-Security (HSTS) | Force HTTPS; prevent downgrade | max-age=31536000; includeSubDomains |
| X-Frame-Options | Prevent clickjacking | DENY or SAMEORIGIN |
| X-Content-Type-Options | Disable MIME sniffing | nosniff |
| Referrer-Policy | Control referrer leakage | strict-origin-when-cross-origin |
| Content-Security-Policy | XSS mitigation | See Section 2 |
| Permissions-Policy | Disable unused browser features | camera=(), microphone=() |
TLS best practices:
- TLS 1.2 minimum; prefer 1.3.
- Strong cipher suites only; disable weak ciphers.
- Certificate pinning for high-security mobile apps (rare for web).
Header implementation checklist: Add middleware or reverse-proxy config. Test with securityheaders.com. Ensure no conflicting headers (e.g., multiple CSP headers merge incorrectly).
Senior rule:
Security headers are cheap. Set them on every response.
INCIDENT RESPONSE FOR ENGINEERS
Breach detection:
- Alerts on anomalous access patterns (e.g., mass export, unusual IP).
- Audit log review for privileged actions.
- User reports of suspicious activity.
Containment:
- Revoke compromised sessions/tokens.
- Disable affected accounts or APIs.
- Isolate affected systems if needed.
Credential rotation:
- Rotate all secrets that may have been exposed:
- API keys
- DB passwords
- OAuth client secrets
- Webhook signing keys
- Invalidate all user sessions; force re-login.
Postmortem:
- Timeline: what happened, when.
- Root cause: how did it happen?
- Impact: what was accessed?
- Action items: what will we fix?
- Blameless: focus on systems, not people.
Communication during incident: Notify stakeholders early. Use status page. Avoid speculation; state facts. Post-incident: share postmortem (sanitized) so org learns.
Regulatory notification: GDPR requires breach notification within 72 hours to supervisory authority. Know your jurisdiction. Have legal contact. Document what was accessed, when, and scope.
Senior rule:
Have a runbook for "we've been breached." Know who to call, what to rotate, what to log.
EXERCISES
-
Threat model your upload flow.
-
Write CSRF defenses for your auth scheme.
-
Define webhook verification + replay protection.
-
Add security headers to your app. Run a scan (e.g., securityheaders.com).
-
Draft an incident response runbook for credential compromise.
APPENDIX — SECURITY CHECKLIST
Before ship: Threat model for new features. OWASP Top 10 reviewed. Security headers set. Secrets not in code. Dependency scan clean. AuthZ on every protected endpoint. Audit logging for privileged actions.
Auth-specific: Session fixation prevented. Tokens short-lived; refresh rotation. Rate limit on auth endpoints.
Incident readiness: Runbook for breach/credential compromise. Contact list for security incidents.
Security champions: Designate security champions per team. They attend security training, review PRs for security, and escalate to security team.
Bug bounty: For public-facing apps, consider a bug bounty program. External researchers find vulns you miss.