05 Expert practice · Expert

What Is Threat Modeling as Code?

3 of 5 in Expert practice · 4 min read · Updated

Read first: Four Question Framework, STRIDE per element

Definition

Threat modeling as code (TMaC) is the practice of describing a system's architecture, data flows and threats in a machine-readable text format, such as YAML, JSON or a small programming language library, and keeping it in version control next to the code. The model is reviewed in pull requests, versioned with releases and can be checked or rendered automatically in CI.

Most threat models age badly. They're drawn in a workshop, saved as a diagram or a document, and slowly drift away from the system as it changes. Threat modeling as code attacks that problem the way infrastructure as code attacked hand-configured servers: put the model in text, in the repository, and change it in the same pull request as the system.

How it works

Instead of a diagram file, the system is described in a structured text format:

# threatmodel.yaml (illustrative)
boundaries:
  internet: {}
  app_vpc: {}
elements:
  customer:   { type: external_entity, boundary: internet }
  web_api:    { type: process, boundary: app_vpc }
  invoices_db:{ type: data_store, boundary: app_vpc, data: [invoices, customer_pii] }
flows:
  - from: customer
    to: web_api
    protocol: https
    authenticated: true
  - from: web_api
    to: invoices_db
    protocol: postgres-tls
threats:
  - id: T2
    element: web_api
    stride: information_disclosure
    statement: Customer can read another customer's invoices by changing the ID
    likelihood: 4
    impact: 5
    treatment: mitigate
    ticket: INV-412

From that file, tooling can render the data flow diagram, run rules ("every flow crossing a trust boundary must be encrypted and authenticated"), generate a checklist of STRIDE per element questions and produce reports.

Tools

  • OWASP pytm: a Python library. You describe elements and flows as Python objects; it generates DFDs, sequence diagrams and a list of threats from a rule set.
  • Threagile: a YAML model of the architecture, with a risk rules engine that produces reports, diagrams and risk tracking.
  • threatspec: threat and mitigation annotations written as comments in the source code itself, collected into a report.
  • JSON from visual tools: OWASP Threat Dragon stores models as JSON, and several commercial tools, ThreatTree included, can export diagrams as JSON to commit to a repository.

Benefits

  • History: git log shows when a trust boundary appeared, who accepted a risk and why.
  • Review: architecture changes and their threat model changes are reviewed together, by the same people.
  • Drift detection: CI can flag a pull request that adds a new service or external call without touching the model.
  • Automation: rules catch repetitive, known issues (unencrypted flows, missing authentication) so people can spend their time on the design-specific ones.
  • Scale: a security team can see the state of hundreds of services' models without opening hundreds of documents.

Limits

  • It can't find what you didn't describe. The most important findings in many threat models come from drawing something nobody had written down, such as a shared credential.
  • Rule output is not a threat model. Generated threats are generic by nature. They're a checklist for the second of the four questions, not an answer to it.
  • It excludes people who don't write code. Product managers, support leads and compliance staff often contribute the best "what can go wrong" ideas, and few of them review YAML.
  • False confidence. A green CI check means the rules passed, not that the design is safe.

Worked example: Invoicely

Invoicely keeps its threat model visual for workshops and exports it as JSON into the repository after each session. Two CI checks run on every pull request:

  1. If the change touches infrastructure definitions, API routes or outbound HTTP clients, and the threat model file hasn't changed, the pipeline posts a reminder asking the author to confirm no new flows or trust boundaries were added.
  2. Every threat scored 15 or above must have either a linked ticket or an accepted-risk entry with an owner.

When the logo-from-URL feature was proposed (the SSRF example), the first check fired on the new HTTP client in the PDF worker, and the discussion happened in the pull request rather than after release.

A practical adoption path

  1. Start with the format your team will actually maintain. A JSON export from a visual tool beats an ambitious YAML schema nobody updates.
  2. Commit the model next to the service it describes.
  3. Add one CI check: "architecture-relevant change without a model change". It catches most drift.
  4. Add rules gradually, starting with ones that would have caught a real past incident.
  5. Keep running human sessions for new systems and major changes; use the code-based model to keep them current in between.

This fits a broader shift-left approach, and is usually a sign of a team reaching the higher levels of threat modeling maturity.

Common mistakes

  • Treating generated threats as the result. Review them, discard what doesn't apply, and add what the rules can't know.
  • A model per repository that nobody owns. Each model needs an owner, just like the code.
  • Blocking merges on the model too early. Start with warnings; hard gates on day one teach people to make empty edits.

Key takeaways

  • Threat modeling as code stores the model as text in the repository, so it changes in the same pull requests as the system it describes.
  • Its strengths are versioning, review, drift detection and automation; its weakness is that it can't think for you.
  • Generated threats from rules are a starting checklist. The valuable findings still come from people discussing the design.
  • A hybrid works well: a visual model for workshops and stakeholders, exported as text into version control for history and CI checks.

Frequently asked questions

What tools are used for threat modeling as code?

Common open-source options include OWASP pytm (a Python library that describes the system and generates diagrams and threats), Threagile (a YAML model with a rules engine and reports) and threatspec (threat annotations in code comments). Several commercial tools also import and export models as JSON or YAML.

Is threat modeling as code better than diagram-based threat modeling?

It is better at versioning, review and automation, and worse at collaborative discussion with people who don't write code. Many teams combine the two: they discuss and draw visually, then keep a text export in the repository.

Can threat modeling as code run in CI/CD?

Yes. Typical checks are that the model file exists for every service, that it was updated when architecture-relevant files changed, that rule-based checks pass (for example no unencrypted flow across a trust boundary), and that diagrams and reports are regenerated.

Does threat modeling as code replace threat modeling workshops?

No. Rule engines find known patterns in what you described. They can't find threats in what you forgot to describe, or design flaws specific to your business logic. Those still come from people asking what can go wrong.

Keep the visual model and the text in step

Every ThreatTree diagram can be downloaded as JSON on any plan, ready to commit next to the code it describes. On Enterprise, DFDs can be imported from OpenAPI, CloudFormation or Terraform, and re-importing after a change gives you a fresh diagram to compare against, which is a quick way to spot drift.

Every plan, Free included, comes with four finished example threat models you can open and change.

Get started free

Get new knowledge-base entries and threat-modeling guides by email.