Agent Wranglers

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

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.