Threat Modeling per Feature
Part 2 →
AuthN: Passwords, Passkeys, MFA
Most security problems are design problems wearing runtime clothes. By the time a SQL injection or a broken access control surfaces in production, the architecture that made it possible was committed weeks or months ago. Threat modeling is how you move that conversation earlier — not into a security team's quarterly review, but into the sprint ceremony where the feature is first sketched out.
The catch is that classical threat modeling is heavy. STRIDE workshops with dedicated security architects, data-flow diagrams in Microsoft Threat Modeling Tool, multi-day facilitated sessions — these belong to the pre-cloud era of annual releases. If your team ships every two weeks, the model has to fit inside a refinement meeting.
This post shows a lightweight, per-feature STRIDE approach that a team of four can run in under an hour.
Why per Feature, Not per Quarter
A quarterly threat model reviews a snapshot of a system that has already changed. The design decisions that introduced risk were made without that input. Per-feature threat modeling puts security thinking at the same moment as the architecture sketch — when changing course is cheap.
The goal is not a perfect threat document. The goal is: does this feature have an obvious, exploitable flaw we haven't thought about? If the answer is yes, you capture it as an acceptance criterion or a separate security task before estimation.
STRIDE in One Paragraph
STRIDE is a mnemonic for six threat categories:
| Letter | Threat | Violated Property |
|---|---|---|
| S | Spoofing | Authentication |
| T | Tampering | Integrity |
| R | Repudiation | Non-repudiation |
| I | Information Disclosure | Confidentiality |
| D | Denial of Service | Availability |
| E | Elevation of Privilege | Authorization |
You walk each component in your feature's data-flow through each row and ask: can an attacker do this here?
The Lightweight Workflow
Three artifacts, thirty to fifty minutes:
- A component sketch — a whiteboard or Excalidraw diagram showing actors, services, data stores, and the trust boundaries between them.
- A STRIDE table — one row per component, six columns.
- Mitigations as acceptance criteria — any "yes" cell becomes a ticket or an AC on the epic.
That is it. No tooling requirement, no dedicated security team needed on day one.
Example: A File-Upload Feature
Suppose you are building a user file-upload feature: a browser uploads a file to an S3-backed API, which stores metadata in a relational database and triggers an async virus scan.
Actor: Browser (untrusted)
→ API Gateway (trust boundary)
→ Upload Service
→ S3 Bucket
→ Metadata DB
→ Scan Queue → Virus Scanner → Scan Results DBRunning STRIDE against the Upload Service component:
| STRIDE | Question | Finding |
|---|---|---|
| Spoofing | Can an attacker upload as another user? | Missing user-id binding in upload token |
| Tampering | Can a file be swapped between upload and scan? | S3 object should be immutable until scan completes |
| Repudiation | Can a user deny uploading a file? | Need audit log: who uploaded, when, from where |
| Information Disclosure | Can one user download another's file? | Pre-signed URL scope must be per-user |
| Denial of Service | Can an attacker exhaust storage? | Need per-user quota enforcement |
| Elevation of Privilege | Can the scanner be tricked into executing code? | Scanner runs in isolated sandbox, no shell access |
Three of those six cells surface real bugs that would otherwise live in production.
Mermaid: Feature Data-Flow with Trust Boundaries
The trust boundaries — Untrusted → DMZ and DMZ → Backend — are where most STRIDE findings cluster. Make them explicit.
Embedding This in Your Sprint Process
The practical cadence that works in high-velocity teams:
Refinement agenda (55 min total):
- Feature walkthrough & acceptance criteria [30 min]
- Draw component sketch on whiteboard [ 5 min]
- Walk STRIDE table together [15 min]
- Capture findings as ACs or tasks [ 5 min]The component sketch does not need to be formal. A box-and-arrow picture on a whiteboard, photographed into Confluence, is enough. The STRIDE table can live in the ticket description.
Threat Libraries Speed Things Up
After a few months you will notice the same threats recurring across features: broken access control on user-scoped resources, missing audit logs, file-type bypass on uploads. Capture these as a team threat library — a short Markdown file in your repo listing common patterns and their standard mitigations. New engineers can run through the library in thirty minutes and immediately contribute to threat reviews.
## Threat Library Entry: User-Scoped Resources
**Threat (STRIDE: I + E):** API endpoint returns records without
verifying the authenticated user owns the record.
**Standard mitigation:**
- All queries must include `WHERE user_id = :current_user_id`
- Integration test: authenticated user A cannot fetch user B's resource
- Code review checklist item: "Is ownership enforced in the query?"Automating the Skeleton
You can generate the STRIDE table skeleton from an OpenAPI spec or a service's README. A simple script reads the paths and produces a prefilled Markdown table — reviewers fill in the findings column, not the boilerplate.
import yaml, sys
STRIDE = ["Spoofing", "Tampering", "Repudiation",
"Info Disclosure", "Denial of Service", "Elevation"]
def generate_stride_table(spec_path: str) -> str:
with open(spec_path) as f:
spec = yaml.safe_load(f)
rows = ["| Component | " + " | ".join(STRIDE) + " |",
"|-----------|" + "---|" * len(STRIDE)]
for path, methods in spec.get("paths", {}).items():
for method in methods:
component = f"`{method.upper()} {path}`"
cols = " | ".join(["?" for _ in STRIDE])
rows.append(f"| {component} | {cols} |")
return "\n".join(rows)
if __name__ == "__main__":
print(generate_stride_table(sys.argv[1]))Run this at the start of a refinement meeting and you have a ready-made table to annotate.
When to Escalate
Lightweight threat modeling catches the obvious. Some features warrant a deeper review:
- Cryptographic key management changes
- Authentication or authorization redesigns
- Cross-tenant data access
- External partner integrations
For these, escalate to a security engineer or schedule a dedicated threat modeling session. The lightweight session still runs first — it surfaces questions faster and makes the deep review more productive.
Key Takeaways
- Threat modeling belongs in refinement, not in a separate security ceremony — the cheapest time to fix a design flaw is before the sprint starts.
- STRIDE gives you a six-category checklist that non-security engineers can apply without deep expertise.
- Draw the trust boundaries explicitly; most findings cluster at those edges.
- Every "yes" cell in the STRIDE table becomes an acceptance criterion or a dedicated task, not a verbal agreement.
- A team threat library of recurring patterns dramatically accelerates future sessions and onboards new engineers quickly.
- Escalate to deeper review for cryptography, AuthN/AuthZ redesigns, and cross-tenant features — lightweight is not a substitute for all security review.
Part 2 →
AuthN: Passwords, Passkeys, MFA