Leg 4 project: a threat model
Why it matters This project pulls Leg 4 together. You will write a real threat model for a product you actually use, as Python data, and the code that checks it against a rubric. You end with a tool you can reuse for any product, and a clear list of the three things you should fix first.
The brief
Pick one product you use: a messaging app, a fitness tracker, a shopping site, a smart speaker. Write its threat model from your point of view as a user in a dictionary called MODEL, then write three functions that check and rank it.
Kept private: your code is graded in your own browser. If you turn on saving across devices, your drafts are also stored on Floati's server, which is one more reason to keep it general. Even so, keep the model general: write "my messages" or "my home address", never real passwords, usernames, addresses or account numbers. A threat model is about the shape of the risk, not the secrets themselves.
The shape of MODEL
MODEL = {
"product": "a fitness tracker app (fitly.example)",
"assets": ["my running routes", "my heart-rate history", "my contacts"],
"adversaries": [
{"who": "advertisers", "wants": "my habits", "capability": "buy data from the app"},
{"who": "a stranger", "wants": "where I live", "capability": "see public route maps"},
],
"threats": [
{"id": "T1", "asset": "my running routes", "adversary": "a stranger",
"what": "public maps show where my runs start, near my home",
"likelihood": 2, "impact": 3},
# ... at least 4 threats
],
"mitigations": [
{"threat": "T1", "action": "hide the first and last 500 m of every route", "owner": "me"},
# ... at least one for every threat
],
}
Each threat's asset must be one of your assets, and each threat's adversary must match one adversary's "who". That makes the model join up: every threat says exactly what it hits and who does it. owner says who does the mitigation: "me" or "product" (things you would ask the company to change).
The rubric
| Check | Problem code if it fails |
|---|---|
| A non-empty product name | no-product |
| At least 3 assets | few-assets |
| At least 2 adversaries | few-adversaries |
| At least 4 threats | few-threats |
| Each threat's asset is in the asset list | unknown-asset:T1 (with that threat's id) |
| Each threat's adversary matches a "who" | unknown-adversary:T1 |
| Likelihood and impact are each 1, 2 or 3 | bad-score:T1 |
| Each threat has at least one mitigation | unmitigated:T1 |
Self-assessment and tutor feedback
The tests check that your model is complete and joins up, and that your functions are right. They cannot judge whether your threats are the right threats. For that, read your top three threats out loud and ask yourself: would someone who knows this product agree these are the biggest risks? You can also press the on-device tutor's code review button for comments on your work. It runs in your browser, so the model stays with you. It is a small model, so treat its notes as prompts to think, not as a verdict. This is why the Leg 4 stamp says "self-assessed".
Tips for a good model
- Use what you learnt: cookies, fingerprints, pixels and consent (Leg 1), how trackers work in code (Leg 2), third parties and leaks on sites (Leg 3), what HTTPS and end-to-end encryption do and do not hide, and where AI features send your text.
- Include at least one ordinary threat (a reused password, a lost phone) and one about the product itself (what it collects, who it shares with).
- Prefer mitigations you will really do this week over perfect ones you will not.
In other languages
JavaScript: the risk ranking with a two-part sort.
const risk = t => t.likelihood * t.impact;
function topThreats(model, n = 3) {
return [...model.threats]
.sort((a, b) => risk(b) - risk(a) || a.id.localeCompare(b.id))
.slice(0, n)
.map(t => t.id);
}
Rust: the same ordering with sort_by.
threats.sort_by(|a, b| risk(b).cmp(&risk(a)).then(a.id.cmp(&b.id)));
Recap
- A threat model as data can be checked by code: complete, joined up and ranked.
- Risk is likelihood times impact; ties are broken by threat id so the order is always the same.
- The tests check structure; you and the tutor judge whether the threats are the right ones.
- Keep your model general and private: describe risks, never write down the secrets.
Fill in MODEL for a product you use (see the shape above): a "product" string, at least 3 "assets", at least 2 "adversaries" (each with non-empty "who", "wants" and "capability"), at least 4 "threats" and a mitigation (with a non-empty "action" and "owner" of "me" or "product") for every threat. Then write:
risk(threat): returnslikelihood * impact.top_threats(model, n=3): returns the ids of thenhighest-risk threats, highest first; when two threats have the same risk, the smaller id (in normal string order) comes first.rubric(model): returns a list of problem codes from the rubric table, or[]when the model passes. Check the four model-wide rules first, then each threat in list order, checking asset, adversary, score, mitigation in that order. Use.getwith defaults so a model with missing keys does not crash.
rubric(MODEL) and top_threats(MODEL).
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.