A list of 50 threats helps no one. Rate each by likelihood and impact, prioritize ruthlessly, turn each into a tracked control, and wire the high-value checks into your pipeline so the fix stays fixed.
You cannot fix everything, so rank. A simple likelihood-x-impact matrix is enough for most teams and avoids the false precision of scoring systems. Reserve heavier models (CVSS for known CVEs, DREAD where you need a number) for when a stakeholder demands one. The goal is a defensible order, not a perfect score.
IMPACT ->
LIKELIHOOD Low Medium High
High Medium High CRITICAL
Medium Low Medium High
Low Low Low Medium
CRITICAL / High -> fix now, block the release if needed
Medium -> backlog with a date
Low -> accept & document, or fix if cheap
Beware "Low likelihood" as a dodge: if the exploit is a single
unauthenticated request, likelihood is not low.DREAD scores a threat on five axes to produce a sortable number. It is subjective — its value is forcing a consistent conversation across a team, not scientific accuracy. Agree on what each score means up front, or two engineers will rate the same threat differently.
Score each 1-3, sum (or average):
Damage how bad if exploited?
Reproducibility how reliably can it be done?
Exploitability how much skill/effort needed?
Affected users how many are hit?
Discoverability how easy to find? (some teams drop this —
assume attackers WILL find it)
UP-01 tampering (overwrite objects): D3 R3 E2 A2 = 10/12 -> High
Sort the list by score; work top-down.Close the loop: every non-accepted threat becomes a ticket in the same backlog as feature work, with an owner and an acceptance test. This is where threat modeling earns its keep — the model is only as good as the fixes it drives. Link the ticket back to the threat id so the model stays traceable.
# threat-model/upload.yaml (updated)
- id: UP-02
stride: Information disclosure
scenario: public bucket + guessable URLs -> read others' images
rating: Critical # High likelihood x High impact
decision: mitigate
control: private bucket + per-object authz + short-lived signed URLs
ticket: SEC-142
test: "GET another user's object URL returns 403" # <- becomes a test
status: in-progress
Definition of done for a threat = the test exists and passes.The best mitigation is one a machine re-checks on every change so the flaw cannot silently return. Where a threat maps to something scannable — a public bucket, a missing header, a vulnerable dependency, hardcoded secrets — encode it as a pipeline gate. This is the bridge from threat modeling to the DevSecOps Pipeline course.
# .github/workflows/security.yml (excerpt)
# Threat UP-02 said "no public buckets" — enforce it as IaC policy, every PR.
jobs:
iac-policy:
steps:
- uses: actions/checkout@v4
- name: Scan Terraform for public buckets / bad IAM
run: |
# Checkov flags S3 buckets that allow public ACLs, etc.
pip install checkov
checkov -d ./infra --hard-fail-on HIGH
headers-and-authz-tests:
steps:
- run: pytest tests/security/ # includes the "403 on other user's object" test
# A modeled threat that isn't tested tends to come back.
# A modeled threat with a failing-build test stays dead.