STRIDE threat analysis for Choreo.ThreatModel.
Automatically generates threats based on element types, data-flow topology, and trust-boundary crossings.
STRIDE categories
| Category | Targets | Question |
|---|---|---|
| Spoofing | External entities, processes | Can someone impersonate this? |
| Tampering | Processes, data stores, flows | Can data be modified? |
| Repudiation | External entities, processes | Can actions be denied? |
| Information Disclosure | Processes, data stores, flows | Can data leak? |
| Denial of Service | Processes, data stores, flows | Can this be overwhelmed? |
| Elevation of Privilege | Processes, data stores | Can an attacker gain access? |
Further reading
Summary
Functions
Returns all paths from external entities to data stores.
Returns all data flows that cross a trust boundary.
Returns data stores that are reachable from an external entity (directly or indirectly).
Generates a heatmap of the threat model based on threat density.
Returns processes that sit in low-trust boundaries but access high-sensitivity data stores.
Calculates a total risk score and qualitative risk rating for the threat model.
Generates STRIDE threats for every element and data flow in the model.
Summarises threats by STRIDE category and severity.
Returns unencrypted data flows that cross a trust boundary.
Validates a threat model and returns a list of issues.
Functions
@spec attack_paths(Choreo.ThreatModel.t()) :: [[Yog.node_id()]]
Returns all paths from external entities to data stores.
Each result is a list of node IDs representing a path from the internet to data at rest. These are the attack vectors that an adversary would follow.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
...> |> Choreo.ThreatModel.add_data_store(:db)
...> |> Choreo.ThreatModel.data_flow(:user, :api)
...> |> Choreo.ThreatModel.data_flow(:api, :db)
iex> paths = Choreo.ThreatModel.Analysis.attack_paths(model)
iex> [:user, :api, :db] in paths
trueThis analysis answers the question: "What are the attack vectors from outside to data at rest?"
@spec cross_boundary_flows(Choreo.ThreatModel.t()) :: [ {Yog.node_id(), Yog.node_id(), String.t() | nil, String.t() | nil} ]
Returns all data flows that cross a trust boundary.
Each result is {from, to, from_boundary, to_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.data_flow(:user, :api)
iex> flows = Choreo.ThreatModel.Analysis.cross_boundary_flows(model)
iex> length(flows)
1
iex> Enum.any?(flows, fn {from, to, _, _} -> from == :user and to == :api end)
trueThis analysis answers the question: "Which data flows cross a trust boundary?"
@spec exposed_data_stores(Choreo.ThreatModel.t()) :: [Yog.node_id()]
Returns data stores that are reachable from an external entity (directly or indirectly).
These are high-value targets because they contain data at rest and are exposed to untrusted input.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_external_entity(:user)
...> |> Choreo.ThreatModel.add_process(:api)
...> |> Choreo.ThreatModel.add_data_store(:db)
...> |> Choreo.ThreatModel.data_flow(:user, :api)
...> |> Choreo.ThreatModel.data_flow(:api, :db)
iex> Choreo.ThreatModel.Analysis.exposed_data_stores(model)
[:db]This analysis answers the question: "Which data stores are reachable from external entities?"
@spec heatmap( Choreo.ThreatModel.t(), keyword() ) :: Choreo.ThreatModel.t()
Generates a heatmap of the threat model based on threat density.
Nodes with more threats will be colored with "hotter" colors from the selected palette.
Options
:palette— Color palette (:heat,:cool,:spectral)- All other options are passed to
stride_threats/2.
@spec high_risk_processes(Choreo.ThreatModel.t()) :: [Yog.node_id()]
Returns processes that sit in low-trust boundaries but access high-sensitivity data stores.
These are risky because compromised process code can leak or tamper with sensitive data.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_process(:api)
...> |> Choreo.ThreatModel.add_data_store(:db, sensitivity: :confidential)
...> |> Choreo.ThreatModel.data_flow(:api, :db)
iex> Choreo.ThreatModel.Analysis.high_risk_processes(model)
[:api]This analysis answers the question: "Which processes access sensitive data from low-trust zones?"
@spec risk_score( Choreo.ThreatModel.t(), keyword() ) :: %{score: number(), rating: atom()}
Calculates a total risk score and qualitative risk rating for the threat model.
Each threat's severity is mapped to a numeric score. The total score is the sum of all individual threat scores.
Severity Weights (Default)
:low— 1:medium— 3:high— 6:critical— 10
Qualitative Ratings (Default)
0—:none1..10—:low11..30—:medium31..70—:high>70—:critical
Options
:weights— keyword list of custom severity weights (e.g.,[low: 2, medium: 4, ...])
Examples
iex> model = Choreo.ThreatModel.new()
...> |> Choreo.ThreatModel.add_trust_boundary("app")
...> |> Choreo.ThreatModel.add_process(:api, boundary: "app")
iex> %{score: score, rating: rating} = Choreo.ThreatModel.Analysis.risk_score(model)
iex> is_number(score)
true
iex> rating in [:none, :low, :medium, :high, :critical]
trueThis analysis answers the question: "What is the overall security risk rating of the architecture?"
@spec stride_threats( Choreo.ThreatModel.t(), keyword() ) :: [ %{ id: String.t(), category: atom(), target: Yog.node_id() | {Yog.node_id(), Yog.node_id()}, description: String.t(), severity: :low | :medium | :high | :critical, mitigation: String.t() } ]
Generates STRIDE threats for every element and data flow in the model.
Returns a list of threat structs:
%{
id: String.t(),
category: :spoofing | :tampering | :repudiation | :information_disclosure | :denial_of_service | :elevation_of_privilege,
target: Yog.node_id(),
description: String.t(),
severity: :low | :medium | :high | :critical,
mitigation: String.t()
}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.data_flow(:user, :api)
iex> threats = Choreo.ThreatModel.Analysis.stride_threats(model)
iex> Enum.any?(threats, & &1.category == :spoofing)
true
iex> Enum.any?(threats, & &1.target == :user)
true
iex> Enum.any?(threats, & match?({:user, :api}, &1.target))
trueThis analysis answers the question: "What threats exist in my architecture?"
@spec threat_summary(Choreo.ThreatModel.t()) :: %{ by_category: %{required(atom()) => %{required(atom()) => non_neg_integer()}}, by_severity: %{required(atom()) => non_neg_integer()}, total: non_neg_integer() }
Summarises threats by STRIDE category and severity.
Returns a map of %{category => %{severity => count}} plus totals.
Useful for dashboards and executive reporting.
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_trust_boundary("app")
...> |> Choreo.ThreatModel.add_process(:api, boundary: "app")
iex> summary = Choreo.ThreatModel.Analysis.threat_summary(model)
iex> summary.total > 0
true
iex> is_map(summary.by_category)
true
iex> is_map(summary.by_severity)
trueThis analysis answers the question: "How are threats distributed by category and severity?"
@spec unencrypted_boundary_flows(Choreo.ThreatModel.t()) :: [ {Yog.node_id(), Yog.node_id()} ]
Returns unencrypted data flows that cross a trust boundary.
These are prime targets for interception and tampering.
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.data_flow(:user, :api)
iex> Choreo.ThreatModel.Analysis.unencrypted_boundary_flows(model)
[{:user, :api}]This analysis answers the question: "Which cross-boundary flows are unencrypted?"
@spec validate(Choreo.ThreatModel.t()) :: [{:error | :warning, String.t()}]
Validates a threat model and returns a list of issues.
Checks for:
- elements not assigned to a trust boundary
- unencrypted cross-boundary flows
- processes without privilege level
- data stores without sensitivity classification
Examples
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_trust_boundary("app")
...> |> Choreo.ThreatModel.add_process(:api, boundary: "app", privilege: :user)
...> |> Choreo.ThreatModel.add_data_store(:db, boundary: "app", sensitivity: :internal)
...> |> Choreo.ThreatModel.data_flow(:api, :db, encrypted: true)
iex> Choreo.ThreatModel.Analysis.validate(model)
[]
iex> model = Choreo.ThreatModel.new()
iex> model = model
...> |> Choreo.ThreatModel.add_process(:api)
iex> issues = Choreo.ThreatModel.Analysis.validate(model)
iex> Enum.any?(issues, fn {_sev, msg} -> String.contains?(msg, "trust boundary") end)
trueThis analysis answers the question: "Is the threat model structurally sound?"