Cybersecurity and Privacy Essentials
Write safer code by understanding common threats and defensive techniques.
Content
Threat Modeling
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Threat Modeling for Flask Web Apps — CS50 Cybersecurity Essentials
Ever deployed a Flask app and felt a chill when someone whispered "production"? That feeling is not paranoia — it is the perfect time for threat modeling.
You already learned how to build dynamic server-side apps with Flask, manage configuration and environments, and write tests before deployment. Threat modeling is the next logical step: instead of just making your app work, you figure out how it could be attacked, then design it so those attacks are hard, expensive, or impossible.
What is threat modeling? And why it matters
Threat modeling is a structured process for identifying, prioritizing, and addressing potential security threats to a system. Think of it as drawing a map of your app with labeled weak points, then deciding which fortifications matter most.
Why bother?
- It focuses effort on the most damaging problems first. Not all bugs are created equal.
- It influences design choices early, which is cheaper than patching after deployment.
- It ties directly into testing and configuration — things you've already practiced in CS50.
Real-life analogy
Imagine your Flask app is a house. You have doors (endpoints), windows (file uploads), a basement safe (database), and a guest list (authentication). Threat modeling asks:
- What windows face the street? (attack surface)
- Which rooms hold valuables? (assets)
- Where do strangers pass through to get inside? (trust boundaries)
Then you decide: do you need bars, an alarm system, or a moat?
Step-by-step threat modeling for a Flask app
- Identify assets
- User data, session tokens, API keys, database credentials, server CPU/time, uploaded files.
- Draw the architecture and trust boundaries
- Client browser, reverse proxy/load balancer, Flask app, database, third-party APIs, cloud object storage.
- Decompose the app
- List endpoints, data flows, serialization points, file upload handlers, templates that render user input.
- Identify threats (use STRIDE)
- Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege.
- Prioritize risks
- Use a simple risk matrix: likelihood vs impact. (Start with the high-impact, likely issues.)
- Design mitigations
- Add authentication, input validation, least privilege, rate limiting, logging, secure configuration.
- Verify and iterate
- Add tests, run scanners, do threat-model reviews when features change.
STRIDE applied to a Flask app (cheat sheet)
| STRIDE | Example threat | Concrete Flask mitigation |
|---|---|---|
| Spoofing | Attacker pretends to be a user | Use strong auth, secure cookies, session signing, MFA when possible |
| Tampering | Uploaded file alters server behavior | Validate file types, store outside web root, scan for malware |
| Repudiation | User denies submitting request | Ensure proper logging, timestamps, non-repudiation where needed |
| Information disclosure | Leak of DB data or secrets | Encrypt data at rest, use HTTPS, limit error messages |
| Denial of Service | Flooding endpoints | Rate limiting, request size limits, connection throttling |
| Elevation of Privilege | User gains admin rights | Enforce role checks server-side, avoid trusting client state |
Mini threat model: a simple Flask app
Scenario: a blog app with user accounts, file uploads for avatars, and an admin dashboard.
Assets
- User passwords and profiles
- Uploaded avatar files
- Admin privileges and dashboard functions
- Database containing posts and comments
Trust boundaries
- Browser vs server
- Reverse proxy vs app
- App vs database
Attack surface highlights
- /login and /register endpoints (authentication)
- /upload-avatar endpoint (file handling)
- Templates that display user content (XSS risk)
- Admin endpoints (privilege separation)
Key threats and quick mitigations
- SQL injection or unsafe queries -> Use parameterized queries or ORM methods (e.g., SQLAlchemy). Never string-concatenate SQL.
- Cross-site scripting (XSS) -> Escape user input in templates, and sanitize HTML if you allow it. Flask/Jinja escapes by default; be careful with |safe.
- CSRF on state-changing endpoints -> Use CSRF tokens (Flask-WTF or Flask-SeaSurf).
- Stolen session cookie -> Set session cookie flags: SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Lax'.
- Dangerous file uploads -> Check MIME types and extensions, store in private object storage, serve via signed URLs.
Code hints (Flask config snippets)
# load from env; you covered this in config & environments
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
# Use CSRF protection
from flask_wtf import CSRFProtect
csrf = CSRFProtect(app)
Prioritizing: risk matrix and testing
You do not need to fix every possible thing at once. Prioritize by impact on users and likelihood of exploit.
Simple prioritization:
- High impact, high likelihood -> fix immediately (e.g., auth bypass, SQLi)
- High impact, low likelihood -> plan mitigations (e.g., data exfiltration via misconfigured backups)
- Low impact, high likelihood -> monitor and mitigate (e.g., spam comments)
Tie this to your tests: add unit and integration tests that assert security properties. For example:
- Automated tests assert that session cookies have secure flags.
- Integration tests simulate CSRF by sending POSTs without tokens.
- Use tools like OWASP ZAP or Nikto to scan deployed endpoints.
Quick checklist before deploy (builds on your previous lessons)
- Secrets and config pulled from environment variables, not hard-coded.
- Session and cookie settings are secure.
- Input validation and escaping are applied where needed.
- File uploads sanitized and stored safely.
- Rate limiting and logging are enabled at the proxy or app level.
- Threat model document stored with the repo and updated when adding endpoints.
Why threat modeling helps your CS50 projects
You already practiced good engineering: structure, tests, and separate configs. Threat modeling adds a security lens to those practices. It turns "just make it work" into "make it work without collapsing under attack." Plus, it makes your project stand out in code reviews and job interviews: you can explain the security decisions you made and why.
This is the moment where the concept finally clicks: security is not a single button but a practice you design into the app.
Key takeaways
- Threat modeling = identify assets, map trust boundaries, list threats, prioritize, mitigate, verify.
- Use STRIDE to systematically classify threats.
- Integrate threat modeling with config and testing: use env vars for secrets, secure cookie settings, CSRF protection, parameterized DB queries, and automated security tests.
- Keep the model alive: update it when you add endpoints or change data flows.
Final memorable insight: building secure software is less about being paranoid and more about being a good planner. Treat threat modeling like your app's flight plan — you don't need to predict every storm, but you should know where the escape hatches are.
If you want, I can generate a one-page threat model template for your Flask project, including a checklist and sample diagrams you can drop into your repo.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!