STRIDE threat-modeling builder on top of Yog.
Choreo.ThreatModel extends dataflow diagrams with security
semantics: trust boundaries, element classification, and automated
STRIDE threat generation.
When to use
Use Choreo.ThreatModel during security review, threat-modeling
sessions, or compliance audits. It automatically generates STRIDE
threats, flags unencrypted boundary crossings, and surfaces attack
paths from external entities to sensitive data stores.
Element types (per Microsoft Threat Modeling Tool)
:external_entity— user, browser, third-party system (outside your control):process— application, service, function (code you own):data_store— database, file, cache, queue (data at rest)
Trust boundaries
Trust boundaries are clusters that group elements by security domain. Data flows that cross a boundary are automatically flagged for elevated scrutiny.
Further reading
- STRIDE Model (Microsoft)
- Threat Modeling: Designing for Security (Shostack)
- OWASP Threat Modeling Cheat Sheet
- Data Flow Diagrams for Threat Modeling
Quick Start
model =
Choreo.ThreatModel.new()
|> Choreo.ThreatModel.add_trust_boundary("internet", label: "Internet", level: 0)
|> Choreo.ThreatModel.add_trust_boundary("app", label: "Application", level: 2)
|> Choreo.ThreatModel.add_trust_boundary("db", label: "Database Zone", level: 3)
|> Choreo.ThreatModel.add_external_entity(:user, label: "User", boundary: "internet")
|> Choreo.ThreatModel.add_process(:api, label: "API Gateway", boundary: "app")
|> Choreo.ThreatModel.add_data_store(:postgres, label: "Postgres", boundary: "db")
|> Choreo.ThreatModel.data_flow(:user, :api, label: "HTTPS request")
|> Choreo.ThreatModel.data_flow(:api, :postgres, label: "SQL query")
threats = Choreo.ThreatModel.Analysis.stride_threats(model)
dot = Choreo.ThreatModel.to_dot(model)Diagram
Summary
Functions
Adds a data store (database, file, cache, queue).
Adds an external entity (user, browser, third-party system).
Adds a process (application, service, function).
Defines a trust boundary (security zone).
Returns the trust boundary name for an element, or nil.
Checks whether a data flow crosses a trust boundary.
Creates a data-flow edge between two elements.
Returns all element IDs in the model.
Returns all elements of a given type.
Returns all data flows as {from, to, label} tuples.
Creates a new empty threat model.
Renders the threat model to DOT format.
Returns the raw Yog.Graph struct underpinning the model.
Returns the numeric trust level of an element's boundary, or nil.
Types
@type t() :: %Choreo.ThreatModel{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional({Yog.node_id(), Yog.node_id()}) => map()}, graph: Yog.graph() }
Functions
@spec add_data_store(t(), Yog.node_id(), keyword()) :: t()
Adds a data store (database, file, cache, queue).
Options
:label— display label:description— tooltip text:boundary— trust boundary name:sensitivity— data sensitivity (:public,:internal,:confidential,:restricted)
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_data_store(model, :db, label: "Postgres", sensitivity: :confidential)
iex> Choreo.ThreatModel.elements(model)
[:db]
iex> Yog.node(model.graph, :db).element_type
:data_store
iex> Yog.node(model.graph, :db).sensitivity
:confidentialDiagram
@spec add_external_entity(t(), Yog.node_id(), keyword()) :: t()
Adds an external entity (user, browser, third-party system).
Options
:label— display label:description— tooltip text:boundary— trust boundary name
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_external_entity(model, :user, label: "User")
iex> Choreo.ThreatModel.elements(model)
[:user]
iex> Yog.node(model.graph, :user).element_type
:external_entity
iex> Yog.node(model.graph, :user).label
"User"Diagram
@spec add_process(t(), Yog.node_id(), keyword()) :: t()
Adds a process (application, service, function).
Options
:label— display label:description— tooltip text:boundary— trust boundary name:privilege— privilege level (e.g.,:user,:admin,:system)
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_process(model, :api, label: "API", privilege: :admin)
iex> Choreo.ThreatModel.elements(model)
[:api]
iex> Yog.node(model.graph, :api).element_type
:process
iex> Yog.node(model.graph, :api).privilege
:adminDiagram
Defines a trust boundary (security zone).
Options
:label— display label:level— numeric trust level (higher = more trusted):style— visual style override:color— border colour override:fillcolor— background colour override
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_trust_boundary(model, "dmz", label: "DMZ", level: 1)
iex> model.clusters["cluster_dmz"].label
"DMZ"
iex> model.clusters["cluster_dmz"].level
1
@spec boundary_of(t(), Yog.node_id()) :: String.t() | nil
Returns the trust boundary name for an element, or nil.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_trust_boundary("app")
...> |> Choreo.ThreatModel.add_process(:api, boundary: "app")
iex> Choreo.ThreatModel.boundary_of(model, :api)
"cluster_app"
@spec crosses_boundary?(t(), Yog.node_id(), Yog.node_id()) :: boolean()
Checks whether a data flow crosses a trust boundary.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_trust_boundary("internet", level: 0)
...> |> Choreo.ThreatModel.add_trust_boundary("app", level: 2)
...> |> Choreo.ThreatModel.add_external_entity(:user, boundary: "internet")
...> |> Choreo.ThreatModel.add_process(:api, boundary: "app")
...> |> Choreo.ThreatModel.add_process(:worker, boundary: "app")
...> |> Choreo.ThreatModel.data_flow(:user, :api)
...> |> Choreo.ThreatModel.data_flow(:api, :worker)
iex> Choreo.ThreatModel.crosses_boundary?(model, :user, :api)
true
iex> Choreo.ThreatModel.crosses_boundary?(model, :api, :worker)
falseDiagram
@spec data_flow(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
Creates a data-flow edge between two elements.
Options
:label— display label:protocol—:http,:https,:grpc,:tcp,:udp, etc.:encrypted— whether the flow is encrypted (default:false)
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
...> |> Choreo.ThreatModel.data_flow(:user, :api, label: "request")
iex> Choreo.ThreatModel.flows(model)
[{:user, :api, "request"}]
iex> model.edge_meta[{:user, :api}].label
"request"
iex> model.edge_meta[{:user, :api}].encrypted
falseDiagram
@spec elements(t()) :: [Yog.node_id()]
Returns all element IDs in the model.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
iex> Enum.sort(Choreo.ThreatModel.elements(model))
[:api, :user]
@spec elements_of_type(t(), atom()) :: [Yog.node_id()]
Returns all elements of a given type.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
iex> Choreo.ThreatModel.elements_of_type(model, :external_entity)
[:user]
iex> Choreo.ThreatModel.elements_of_type(model, :process)
[:api]
iex> Choreo.ThreatModel.elements_of_type(model, :data_store)
[]
@spec flows(t()) :: [{Yog.node_id(), Yog.node_id(), number()}]
Returns all data flows as {from, to, label} tuples.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
...> |> Choreo.ThreatModel.data_flow(:user, :api)
iex> Choreo.ThreatModel.flows(model)
[{:user, :api, ""}]
@spec new() :: t()
Creates a new empty threat model.
Examples
iex> model = Choreo.ThreatModel.new()
iex> Choreo.ThreatModel.elements(model)
[]
iex> Choreo.ThreatModel.flows(model)
[]
Renders the threat model to DOT format.
Options
:theme—:default,:dark, or aChoreo.Themestruct
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user, label: "User")
...> |> Choreo.ThreatModel.add_process(:api, label: "API")
...> |> Choreo.ThreatModel.data_flow(:user, :api, label: "HTTPS")
iex> dot = Choreo.ThreatModel.to_dot(model)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "User")
true
iex> String.contains?(dot, "API")
true
Returns the raw Yog.Graph struct underpinning the model.
Examples
iex> model = Choreo.ThreatModel.new()
iex> graph = Choreo.ThreatModel.to_graph(model)
iex> graph.kind
:directed
@spec trust_level(t(), Yog.node_id()) :: integer() | nil
Returns the numeric trust level of an element's boundary, or nil.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_trust_boundary("dmz", level: 1)
...> |> Choreo.ThreatModel.add_process(:api, boundary: "dmz")
iex> Choreo.ThreatModel.trust_level(model, :api)
1