Threat modeling
Skill vindreshsingh/engineering-skills/skills/threat-modeling
Finds security risks at design time — before code exists — by mapping the system, enumerating how it could be attacked, and deciding what to do about each threat. Use when designing a feature that touches auth, data, money, or external input, when changing a trust boundary, or when a security-sensitive change needs a structured risk pass rather than a code-level review.From its SKILL.md
npx -y skills add vindreshsingh/engineering-skills --skill threat-modelingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.2 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
Threat Modeling
Most security work happens too late — at code review ([[hardening]]) or in production ([[incident-response]]), after the expensive decisions are already baked in. Threat modeling moves the security thinking to design time: look at what you're about to build, ask how would someone attack this?, and decide — mitigate, accept, or redesign — before a line is written. It answers four questions: what are we building, what can go wrong, what are we going to do about it, and did we do a good job?
This is design-phase security, distinct from [[hardening]] (finding issues in a written diff) and [[agent-guardrails]] (runtime blocking of dangerous actions). It feeds those: the model says where to harden and what the guardrails must protect.
When to Use
- Designing a feature touching authentication, authorization, payments, PII, or secrets
- Adding or changing a trust boundary — a new external input, integration, upload, or service call
- A [[spec-first]] / [[interface-design]] pass on anything security-sensitive, before implementation
- Before a risky [[migration-path]] that moves or exposes data differently
- A security-sensitive change needs a structured risk pass, not just a code skim
Skip when the change has no security-relevant surface (internal refactor, copy change, styling) — don't ceremony-model a CSS tweak. For an already-written diff, use [[hardening]]; for a live attack or breach, use [[incident-response]].
Not a substitute for [[hardening]] (code-level verification) — threat modeling decides what matters; hardening confirms the code got it right.
Process
1. Model what you're building — draw the data flow
Sketch the system: components, data stores, external entities, and the trust boundaries they cross (user→app, app→DB, app→third-party, internet→service). You can't reason about attacks you can't see; a simple data-flow diagram beats prose. Mark where data is most sensitive and where input is least trusted.
2. Enumerate threats — be systematic, not lucky
Walk each element and boundary with a checklist so you don't just list the threats you happened to think of. STRIDE is the common lens:
| Category | The threat | Ask |
|---|---|---|
| Spoofing | Pretending to be someone else | Can identity be faked? Is auth enforced here? |
| Tampering | Unauthorized modification | Can data/requests be altered in transit or at rest? |
| Repudiation | Denying an action | Is there an audit trail for sensitive actions? |
| Info disclosure | Leaking data | Can someone read data they shouldn't? Over-broad responses, logs, errors? |
| Denial of service | Making it unavailable | Can input exhaust resources? Rate limits? |
| Elevation of privilege | Gaining more access | Can a normal user reach admin actions? IDOR? |
Pay special attention to untrusted input (every external field, file, header, webhook) and authorization at each boundary (authentication ≠ authorization — check both).
3. Rate each threat by risk
Score roughly by likelihood × impact. A trivially-exploitable PII leak outranks a theoretical DoS needing insider access. You're prioritizing, not writing a thesis — high / medium / low is enough to sequence the response.
4. Decide a response for every threat — the four options
For each threat, pick one and write it down:
- Mitigate — add a control (authz check, validation, rate limit, encryption, signed token)
- Eliminate — redesign so the threat can't exist (don't store the secret; don't accept the input)
- Transfer — push it to a system built for it (managed auth, a payment processor, a WAF)
- Accept — consciously, with a recorded reason and an owner (only for low risk)
"Mitigate" turns into concrete work items for the build; "accept" is a decision, not an oversight — record it ([[decision-docs]]).
5. Don't forget abuse, not just bugs
Model misuse: a logged-in user attacking other users' data (IDOR), automation abusing a free tier, a malicious file upload, prompt injection into an LLM feature ([[llm-feature-engineering]]). Many real breaches are authorized users doing unauthorized things, not classic exploits.
6. Feed the result into build and verification
Each mitigation becomes a task and an acceptance criterion. Hand the list to [[hardening]] as the review checklist for the diff, and to [[test-first]] so high-risk threats get a test that proves the control works. Note runtime invariants for [[agent-guardrails]] / [[observability]] (alerting on the attack signature).
7. Validate the model — did we do a good job?
Re-check: every trust boundary enumerated, every untrusted input has a validation decision, every sensitive action has authz and an audit trail, and every threat has a recorded response. A model with "no threats found" on an auth/payments feature is a model that wasn't done.
Common Rationalizations
- "We'll catch it in code review." — Review finds bugs in what you built; threat modeling changes what you build. Some fixes are impossible to bolt on later.
- "It's internal, no one will attack it." — Internal users, compromised creds, and SSRF make "internal" a weak boundary; model it.
- "Authentication covers it." — Authentication is who you are; authorization is what you may do. IDOR lives in that gap.
- "STRIDE is overkill here." — Then it's a five-minute pass; the checklist exists so you don't miss the category you didn't think of.
- "No threats found." — On an auth/data/money surface that almost always means the model was too shallow, not that it's safe.
- "We'll just accept the risk." — Acceptance is valid only when written down with a reason and an owner; silent acceptance is negligence.
Red Flags
- No data-flow / trust-boundary picture — modeling from memory
- Threats listed ad hoc instead of walked through a checklist (STRIDE or equivalent)
- Untrusted input with no explicit validation decision
- Authentication present but per-resource authorization unchecked (IDOR risk)
- Sensitive actions with no audit trail
- Threats enumerated but several left with no recorded response
- "Accepted" risks with no reason, owner, or revisit date
Verification
- System modeled with components, data stores, external entities, and trust boundaries
- Threats enumerated systematically (STRIDE or equivalent), not ad hoc
- Untrusted inputs and per-boundary authorization explicitly examined
- Each threat rated by likelihood × impact and prioritized
- Every threat has a recorded response (mitigate / eliminate / transfer / accept-with-owner)
- Abuse/misuse cases (IDOR, upload, automation, prompt injection) considered, not just bugs
- Mitigations handed to [[test-first]] (high-risk tests) and [[hardening]] (review checklist); accepted risks in [[decision-docs]]
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.