APS-1Recommended
Plan attacks before building
Write a threat model before building a change that touches money, accounts, personal data or a new outside connection. A second person reviews it before the change can merge.
How to do it
Make it a gate in the design step, not a document written after release. Answer four questions in a short file next to the code. What are we building? What can go wrong? What will we do about it? Did we do a good job? List the data, the money and the actions at stake. Note who could abuse each one, such as a stranger, a customer or a staff member. Start from the data-flow diagram, find threats with STRIDE and LINDDUN and add abuse cases. Give each threat a decision, such as fix, accept or move elsewhere, and turn each fix into a test. Mark the sensitive folders in one list, so the build knows when a model is due. Update the model when the design changes, and review it at least every 12 months. Accepted threats go into the risk register.
Examples
YAML example
# threats/checkout.yaml
feature: checkout
reviewed: 2026-10-14
reviewer: lena.ek
covers: [src/payments/, src/orders/]
threats:
- id: T1
what: A customer changes the price in the request
stride: tampering
decision: fix
test: tests/security/price-from-server.test
- id: T2
what: A stolen card is tested with many small orders
stride: spoofing
decision: accept
risk: RISK-12
JavaScript example
const MAX_AGE_DAYS = 365;
export const threatModelProblems = (changedFiles, sensitiveFolders, models, today = new Date()) => changedFiles
.filter(file => sensitiveFolders.some(folder => file.startsWith(folder)))
.flatMap(file => {
const model = models.find(m => m.covers.some(folder => file.startsWith(folder)));
if (!model) return [`${file}: no threat model`];
const ageDays = (today - new Date(model.reviewed)) / 86_400_000;
return ageDays > MAX_AGE_DAYS ? [`${file}: threat model for ${model.feature} is older than 12 months`] : [];
});
Python example
from datetime import date
MAX_AGE_DAYS = 365
def threat_model_problems(changed_files: list[str], sensitive_folders: list[str], models: list[dict], today: date) -> list[str]:
problems = []
for file in (f for f in changed_files if any(f.startswith(s) for s in sensitive_folders)):
model = next((m for m in models if any(file.startswith(f) for f in m["covers"])), None)
if model is None:
problems.append(f"{file}: no threat model")
elif (today - model["reviewed"]).days > MAX_AGE_DAYS:
problems.append(f"{file}: threat model for {model['feature']} is older than 12 months")
return problems
How to check
The build fails a pull request that changes a listed sensitive folder, unless that folder's threat model was reviewed within 12 months. Every threat with the decision fix links to a test that exists.
Sources
Related rules
- TST-1 Test what would hurt to break in Testing
- CMP-10 Screen every new feature for a DPIA in Compliance and governance
- CMP-39 Keep a risk register with treatment dates in Compliance and governance
- APS-93 Draw the data flows and their trust boundaries in Application security
- APS-94 Find threats at every boundary with STRIDE and LINDDUN in Application security
- APS-95 Write abuse cases next to user stories in Application security
- APS-96 Pick an ASVS level for each app in Application security
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/application-security/threat-modelling/threat-model">Agent Wranglers application security playbook</a>. In code you write, add a comment next to it with the same link.