Choreo.ThreatModel (Choreo v0.7.1)

Copy Markdown View Source

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

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

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; user [label="User", penwidth="2.0", fillcolor="#64748b", shape="box"]; api [label="API Gateway", fillcolor="#3b82f6", shape="circle"]; postgres [label="Postgres", fillcolor="#f59e0b", shape="cylinder"]; subgraph cluster_app { label="Application"; style=dashed; fillcolor="#f8fafc"; color="#ef4444"; api; } subgraph cluster_db { label="Database Zone"; style=dashed; fillcolor="#f8fafc"; color="#ef4444"; postgres; } subgraph cluster_internet { label="Internet"; style=dashed; fillcolor="#f8fafc"; color="#ef4444"; user; } user -> api [label="HTTPS request", style="dashed", penwidth="2.0", fontcolor="#ef4444", color="#ef4444"]; api -> postgres [label="SQL query", style="dashed", penwidth="2.0", fontcolor="#ef4444", color="#ef4444"]; }

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 edges with their metadata as {from, to, weight, meta} tuples.

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.

Collapses parallel edges into a simple Graph for algorithm analysis.

Returns the numeric trust level of an element's boundary, or nil.

Types

t()

@type t() :: %Choreo.ThreatModel{
  clusters: %{required(String.t()) => map()},
  edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()},
  graph: Yog.Multi.Graph.t()
}

Functions

add_data_store(model, id, opts \\ [])

Adds a data store (database, file, cache, queue).

Options

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
:confidential

Diagram

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; db [label="Postgres [confidential]", fillcolor="#f59e0b", shape="cylinder"]; }

add_external_entity(model, id, opts \\ [])

Adds an external entity (user, browser, third-party system).

Options

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

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; user [label="User", penwidth="2.0", fillcolor="#64748b", shape="box"]; }

add_process(model, id, opts \\ [])

Adds a process (application, service, function).

Options

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
:admin

Diagram

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; api [label="API (admin)", fillcolor="#3b82f6", shape="circle"]; }

add_trust_boundary(model, name, opts \\ [])

@spec add_trust_boundary(t(), String.t(), keyword()) :: t()

Defines a trust boundary (security zone).

Options

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

boundary_of(threat_model, id)

@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"

crosses_boundary?(model, from, to)

@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)
false

Diagram

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; user [label="user", penwidth="2.0", fillcolor="#64748b", shape="box"]; worker [label="worker", fillcolor="#3b82f6", shape="circle"]; api [label="api", fillcolor="#3b82f6", shape="circle"]; subgraph cluster_app { label="cluster_app"; style=dashed; fillcolor="#f8fafc"; color="#ef4444"; worker; api; } subgraph cluster_internet { label="cluster_internet"; style=dashed; fillcolor="#f8fafc"; color="#ef4444"; user; } user -> api [label="", style="dashed", penwidth="2.0", fontcolor="#ef4444", color="#ef4444"]; api -> worker [label="", penwidth="1.0", fontcolor="#64748b", color="#64748b"]; }

data_flow(model, from, to, opts \\ [])

Creates a data-flow edge between two elements.

Options

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
false

Diagram

digraph G { graph [rankdir=LR, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=box, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; user [label="user", penwidth="2.0", fillcolor="#64748b", shape="box"]; api [label="api", fillcolor="#3b82f6", shape="circle"]; user -> api [label="request", penwidth="1.0", fontcolor="#64748b", color="#64748b"]; }

edges_with_meta(threat_model)

@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.

elements(threat_model)

@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]

elements_of_type(threat_model, type)

@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)
[]

flows(threat_model)

@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, ""}]

new()

@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)
[]

theme(name \\ :default, overrides \\ [])

@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

to_dot(model, opts \\ [])

@spec to_dot(
  t(),
  keyword()
) :: String.t()

Renders the threat model to DOT format.

Options

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

to_graph(threat_model)

@spec to_graph(t()) :: Yog.graph()

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

to_simple_graph(threat_model, opts \\ [])

@spec to_simple_graph(
  t(),
  keyword()
) :: Yog.Graph.t()

Collapses parallel edges into a simple Graph for algorithm analysis.

trust_level(model, id)

@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