# `Choreo.ThreatModel`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/threat_model.ex#L1)

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)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats)
  * [Threat Modeling: Designing for Security (Shostack)](https://shostack.org/books/threat-modeling-book)
  * [OWASP Threat Modeling Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html)
  * [Data Flow Diagrams for Threat Modeling](https://learn.microsoft.com/en-us/training/modules/tm-create-a-threat-model-using-foundational-data-flow-diagram-elements/)

## 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

<div class="graphviz">
  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"];
  }
</div>

# `t`

```elixir
@type t() :: %Choreo.ThreatModel{
  clusters: %{required(String.t()) =&gt; map()},
  edge_meta: %{optional(Yog.Multi.Graph.edge_id()) =&gt; 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()
}
```

# `add_data_store`

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

## Options

* `:sensitivity`

* `:retention`

* `:controls` (list of `t:atom/0`) - The default value is `[]`.

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:boundary` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t: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
    :confidential

## Diagram

<div class="graphviz">
  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"];
  }
</div>

# `add_external_entity`

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

## Options

* `:role`

* `:privilege`

* `:controls` (list of `t:atom/0`) - The default value is `[]`.

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:boundary` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t: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

<div class="graphviz">
  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"];
  }
</div>

# `add_process`

Adds a process (application, service, function).

## Options

* `:privilege`

* `:controls` (list of `t:atom/0`) - The default value is `[]`.

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:boundary` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t: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
    :admin

## Diagram

<div class="graphviz">
  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"];
  }
</div>

# `add_trust_boundary`

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

Defines a trust boundary (security zone).

## Options

* `:label` (`t:String.t/0`)

* `:level` (`t:integer/0`)

* `:style` (`t:String.t/0`)

* `:color` (`t:String.t/0`)

* `:fillcolor` (`t: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

# `blast_radius`

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

# `boundary_matrix`

```elixir
@spec boundary_matrix(t()) :: map()
```

Summarises data flows between trust boundaries.
Delegates to `Choreo.ThreatModel.Analysis.boundary_matrix/1`.

# `boundary_of`

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

# `clear_highlight`

```elixir
@spec clear_highlight(t()) :: t()
```

Clears the current scenario / attack path highlights.

# `control_gaps`

```elixir
@spec control_gaps(t()) :: [map()]
```

Returns inferred security control gaps.
Delegates to `Choreo.ThreatModel.Analysis.control_gaps/1`.

# `crosses_boundary?`

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

## Diagram

<div class="graphviz">
  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", fillcolor="#3b82f6", shape="circle"];
    user [label="user", penwidth="2.0", fillcolor="#64748b", shape="box"];
    worker [label="worker", fillcolor="#3b82f6", shape="circle"];

    subgraph cluster_app {
      label="app";
      style=dashed;
      fillcolor="#f8fafc";
      color="#ef4444";
      api;
      worker;
    }
    subgraph cluster_internet {
      label="internet";
      style=dashed;
      fillcolor="#f8fafc";
      color="#ef4444";
      user;
    }
    api -> worker [label="", penwidth="1.0", fontcolor="#64748b", color="#64748b"];
    user -> api [label="", style="dashed", penwidth="2.0", fontcolor="#ef4444", color="#ef4444"];
  }
</div>

# `data_flow`

Creates a data-flow edge between two elements.

## Options

* `:label` (`t:String.t/0`)

* `:protocol`

* `:encrypted` (`t:boolean/0`) - The default value is `false`.

* `:authenticated` (`t:boolean/0`) - The default value is `false`.

* `:data`

* `:sensitivity`

* `:controls` (list of `t:atom/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
    false

## Diagram

<div class="graphviz">
  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"];
  }
</div>

# `edges_with_meta`

```elixir
@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`

```elixir
@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`

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

# `entry_points`

```elixir
@spec entry_points(t()) :: [map()]
```

Identifies external entry points entering trusted zones.
Delegates to `Choreo.ThreatModel.Analysis.entry_points/1`.

# `exfiltration_paths`

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

# `exit_points`

```elixir
@spec exit_points(t()) :: [map()]
```

Identifies egress exit points leaving trusted zones.
Delegates to `Choreo.ThreatModel.Analysis.exit_points/1`.

# `flows`

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

# `highlight_attack_paths`

```elixir
@spec highlight_attack_paths(t(), keyword()) :: t()
```

Highlights attack paths in the model by setting `:highlighted_nodes` and `:highlighted_edges`.
Delegates to `Choreo.ThreatModel.Analysis.highlight_attack_paths/2`.

# `new`

```elixir
@spec new(keyword()) :: t()
```

Creates a new empty threat model.

## Options

  * `:strict` — if `true`, `data_flow/4` raises when an endpoint does not
    already exist. Default `false` (auto-creates missing elements as `:process`).

## Examples

    iex> model = Choreo.ThreatModel.new()
    iex> Choreo.ThreatModel.elements(model)
    []
    iex> Choreo.ThreatModel.flows(model)
    []

# `prioritized_findings`

```elixir
@spec prioritized_findings(t(), keyword()) :: [map()]
```

Returns prioritized security-review findings.
Delegates to `Choreo.ThreatModel.Analysis.prioritized_findings/2`.

# `residual_risk_score`

```elixir
@spec residual_risk_score(t(), keyword()) :: map()
```

Calculates residual risk after applied controls.
Delegates to `Choreo.ThreatModel.Analysis.residual_risk_score/2`.

# `theme`

```elixir
@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

# `threats_for`

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

# `to_dot`

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

Renders the threat model to DOT format.

## Options

  * `:theme` — `:default`, `:dark`, or a `Choreo.Theme` struct

## 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`

```elixir
@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_markdown`

```elixir
@spec to_markdown(t(), keyword()) :: String.t()
```

Generates a GitHub Flavored Markdown threat table.
Delegates to `Choreo.ThreatModel.Analysis.to_markdown/2`.

# `to_mermaid`

```elixir
@spec to_mermaid(t(), keyword()) :: String.t()
```

Renders the threat model to Mermaid.js flowchart syntax.

## Options

  * `:theme` — `:default`, `:dark`, `:warm`, `:forest`, `:ocean`, or a `Choreo.Theme` struct
  * `: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

# `to_plantuml`

```elixir
@spec to_plantuml(t(), keyword()) :: String.t()
```

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

# `to_sequence`

```elixir
@spec to_sequence(t(), keyword()) :: String.t()
```

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

# `to_simple_graph`

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

Collapses parallel edges into a simple Graph for algorithm analysis.

# `trust_level`

```elixir
@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

# `unmitigated_threats`

```elixir
@spec unmitigated_threats(t(), keyword()) :: [map()]
```

Returns only unmitigated threats.
Delegates to `Choreo.ThreatModel.Analysis.unmitigated_threats/2`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
