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).
Computes the downstream blast radius if element_id is compromised.
Delegates to Choreo.ThreatModel.Analysis.blast_radius/2.
Summarises data flows between trust boundaries.
Delegates to Choreo.ThreatModel.Analysis.boundary_matrix/1.
Returns the trust boundary name for an element, or nil.
Clears the current scenario / attack path highlights.
Returns inferred security control gaps.
Delegates to Choreo.ThreatModel.Analysis.control_gaps/1.
Checks whether a data flow crosses a trust boundary.
Creates a data-flow edge between two elements.
Returns all edges with their metadata as {from, to, weight, meta} tuples.
Returns all element IDs in the model.
Returns all elements of a given type.
Identifies external entry points entering trusted zones.
Delegates to Choreo.ThreatModel.Analysis.entry_points/1.
Returns sensitive-data paths that can leave the system toward external entities.
Delegates to Choreo.ThreatModel.Analysis.exfiltration_paths/2.
Identifies egress exit points leaving trusted zones.
Delegates to Choreo.ThreatModel.Analysis.exit_points/1.
Returns all data flows as {from, to, label} tuples.
Highlights attack paths in the model by setting :highlighted_nodes and :highlighted_edges.
Delegates to Choreo.ThreatModel.Analysis.highlight_attack_paths/2.
Creates a new empty threat model.
Returns prioritized security-review findings.
Delegates to Choreo.ThreatModel.Analysis.prioritized_findings/2.
Calculates residual risk after applied controls.
Delegates to Choreo.ThreatModel.Analysis.residual_risk_score/2.
Returns a theme for Choreo.ThreatModel.
Returns threats targeting a specific element or flow.
Delegates to Choreo.ThreatModel.Analysis.threats_for/3.
Renders the threat model to DOT format.
Returns the raw Yog.Graph struct underpinning the model.
Generates a GitHub Flavored Markdown threat table.
Delegates to Choreo.ThreatModel.Analysis.to_markdown/2.
Renders the threat model to Mermaid.js flowchart syntax.
Renders the data flows in a threat model to a PlantUML sequence diagram string.
Renders the data flows in a threat model to a Mermaid sequence diagram string.
Collapses parallel edges into a simple Graph for algorithm analysis.
Returns the numeric trust level of an element's boundary, or nil.
Returns only unmitigated threats.
Delegates to Choreo.ThreatModel.Analysis.unmitigated_threats/2.
Types
@type t() :: %Choreo.ThreatModel{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()}, graph: Yog.Multi.Graph.t(), highlighted_edges: [ Yog.Multi.Graph.edge_id() | {Yog.node_id(), Yog.node_id()} ], highlighted_nodes: [Yog.node_id()], strict: boolean() }
Functions
Adds a data store (database, file, cache, queue).
Options
:sensitivity:retention:controls(list ofatom/0) - The default value is[].:label(String.t/0):description(String.t/0):boundary(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
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> Map.get(model.graph.nodes, :db).element_type
:data_store
iex> Map.get(model.graph.nodes, :db).sensitivity
:confidentialDiagram
Adds an external entity (user, browser, third-party system).
Options
:role:privilege:controls(list ofatom/0) - The default value is[].:label(String.t/0):description(String.t/0):boundary(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_external_entity(model, :user, label: "User")
iex> Choreo.ThreatModel.elements(model)
[:user]
iex> Map.get(model.graph.nodes, :user).element_type
:external_entity
iex> Map.get(model.graph.nodes, :user).label
"User"Diagram
Adds a process (application, service, function).
Options
:privilege:controls(list ofatom/0) - The default value is[].:label(String.t/0):description(String.t/0):boundary(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
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> Map.get(model.graph.nodes, :api).element_type
:process
iex> Map.get(model.graph.nodes, :api).privilege
:adminDiagram
Defines a trust boundary (security zone).
Options
:label(String.t/0):level(integer/0):style(String.t/0):color(String.t/0):fillcolor(String.t/0)
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 blast_radius(t(), Yog.node_id()) :: map()
Computes the downstream blast radius if element_id is compromised.
Delegates to Choreo.ThreatModel.Analysis.blast_radius/2.
Summarises data flows between trust boundaries.
Delegates to Choreo.ThreatModel.Analysis.boundary_matrix/1.
@spec boundary_of(t(), Yog.node_id()) :: String.t() | nil
Returns the trust boundary name for an element, or nil.
The internal cluster_ prefix is stripped so the returned name matches
the name originally passed to add_trust_boundary/3.
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)
"app"
Clears the current scenario / attack path highlights.
Returns inferred security control gaps.
Delegates to Choreo.ThreatModel.Analysis.control_gaps/1.
@spec crosses_boundary?(t(), Yog.node_id(), Yog.node_id()) :: boolean()
Checks whether a data flow crosses a trust boundary.
Returns false if either element has no assigned boundary, since a
crossing requires both sides to be in defined security zones.
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
Creates a data-flow edge between two elements.
Options
:label(String.t/0):protocol:encrypted(boolean/0) - The default value isfalse.:authenticated(boolean/0) - The default value isfalse.:data:sensitivity:controls(list ofatom/0) - The default value is[].
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> [{_, _, _, meta}] = Choreo.ThreatModel.edges_with_meta(model)
iex> meta.label
"request"
iex> meta.encrypted
falseDiagram
@spec edges_with_meta(t()) :: [{Yog.node_id(), Yog.node_id(), any(), map()}]
Returns all edges with their metadata as {from, to, weight, meta} tuples.
@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)
[]
Identifies external entry points entering trusted zones.
Delegates to Choreo.ThreatModel.Analysis.entry_points/1.
@spec exfiltration_paths(t(), keyword()) :: [[Yog.node_id()]]
Returns sensitive-data paths that can leave the system toward external entities.
Delegates to Choreo.ThreatModel.Analysis.exfiltration_paths/2.
Identifies egress exit points leaving trusted zones.
Delegates to Choreo.ThreatModel.Analysis.exit_points/1.
@spec flows(t()) :: [{Yog.node_id(), Yog.node_id(), any()}]
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, ""}]
Highlights attack paths in the model by setting :highlighted_nodes and :highlighted_edges.
Delegates to Choreo.ThreatModel.Analysis.highlight_attack_paths/2.
Creates a new empty threat model.
Options
:strict— iftrue,data_flow/4raises when an endpoint does not already exist. Defaultfalse(auto-creates missing elements as:process).
Examples
iex> model = Choreo.ThreatModel.new()
iex> Choreo.ThreatModel.elements(model)
[]
iex> Choreo.ThreatModel.flows(model)
[]
Returns prioritized security-review findings.
Delegates to Choreo.ThreatModel.Analysis.prioritized_findings/2.
Calculates residual risk after applied controls.
Delegates to Choreo.ThreatModel.Analysis.residual_risk_score/2.
@spec theme(atom(), keyword()) :: Choreo.Theme.t()
Returns a theme for Choreo.ThreatModel.
Examples
iex> theme = Choreo.ThreatModel.theme(:default, graph_rankdir: :tb)
iex> theme.graph_rankdir
:tb
@spec threats_for(t(), Yog.node_id() | {Yog.node_id(), Yog.node_id()}, keyword()) :: [ map() ]
Returns threats targeting a specific element or flow.
Delegates to Choreo.ThreatModel.Analysis.threats_for/3.
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
Generates a GitHub Flavored Markdown threat table.
Delegates to Choreo.ThreatModel.Analysis.to_markdown/2.
Renders the threat model to Mermaid.js flowchart syntax.
Options
:theme—:default,:dark,:warm,:forest,:ocean, or aChoreo.Themestruct:direction—:lr(default),:td,:rl,:bt
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> mermaid = Choreo.ThreatModel.to_mermaid(model)
iex> String.contains?(mermaid, "graph LR")
true
Renders the data flows in a threat model to a PlantUML sequence diagram string.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_external_entity(model, :user, label: "Customer")
iex> model = Choreo.ThreatModel.add_process(model, :web_api, label: "Web API")
iex> model = Choreo.ThreatModel.data_flow(model, :user, :web_api, label: "HTTPS login")
iex> puml = Choreo.ThreatModel.to_plantuml(model)
iex> String.contains?(puml, "@startuml")
true
iex> String.contains?(puml, "user -> web_api")
true
Renders the data flows in a threat model to a Mermaid sequence diagram string.
External entities render as actor, processes and data stores as participant.
Unencrypted flows crossing trust boundaries use dashed arrows (-->) to
visually flag insecure communication.
Options
No options are currently used, but the argument is reserved for future use.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = Choreo.ThreatModel.add_external_entity(model, :user, label: "Customer")
iex> model = Choreo.ThreatModel.add_process(model, :web_api, label: "Web API")
iex> model = Choreo.ThreatModel.data_flow(model, :user, :web_api, label: "HTTPS login")
iex> seq = Choreo.ThreatModel.to_sequence(model)
iex> String.contains?(seq, "sequenceDiagram")
true
iex> String.contains?(seq, "actor user")
true
@spec to_simple_graph(t(), keyword()) :: Yog.Graph.t()
Collapses parallel edges into a simple Graph for algorithm analysis.
@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
Returns only unmitigated threats.
Delegates to Choreo.ThreatModel.Analysis.unmitigated_threats/2.