Threat modeling is the habit of asking "what can go wrong?" before attackers do. For a small project it can fit in an hour and a sheet of paper.
Step 1: draw the system
Sketch the actors, the components (web app, database, third-party APIs) and the data flows between them. Mark every trust boundary: the internet to your server, the app to the database, your code to external services.
Step 2: apply STRIDE at each boundary
- Spoofing: can someone pretend to be a user or a service? (authentication)
- Tampering: can data be modified in transit or at rest? (integrity, TLS, signatures)
- Repudiation: can an action be denied later? (audit logs)
- Information disclosure: what leaks if this component is compromised? (encryption, least privilege)
- Denial of service: what happens under load or abuse? (rate limiting, timeouts)
- Elevation of privilege: can a normal user reach admin functions? (authorization checks)
Step 3: write threats as sentences
"An attacker can submit the contact form thousands of times to flood the mailbox." A concrete sentence is easy to prioritize and to test.
Step 4: decide what to do
For each threat choose: mitigate, accept, transfer or avoid. Rank by impact and likelihood, and implement the top few. In the contact form example, a honeypot field, a signed token and per-IP rate limiting cover the threat cheaply.
Keep it alive
Store the diagram and the threat list in the repository and revisit them when you add a feature that crosses a new boundary. A small, current model beats a perfect one that nobody opens.