Choreo.ThreatModel.Analysis (Choreo v0.7.0)

Copy Markdown View Source

STRIDE threat analysis for Choreo.ThreatModel.

Automatically generates threats based on element types, data-flow topology, and trust-boundary crossings.

STRIDE categories

CategoryTargetsQuestion
SpoofingExternal entities, processesCan someone impersonate this?
TamperingProcesses, data stores, flowsCan data be modified?
RepudiationExternal entities, processesCan actions be denied?
Information DisclosureProcesses, data stores, flowsCan data leak?
Denial of ServiceProcesses, data stores, flowsCan this be overwhelmed?
Elevation of PrivilegeProcesses, data storesCan 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).

Returns processes that sit in low-trust boundaries but access high-sensitivity data stores.

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

attack_paths(model)

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

This analysis answers the question: "What are the attack vectors from outside to data at rest?"

cross_boundary_flows(model)

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

This analysis answers the question: "Which data flows cross a trust boundary?"

exposed_data_stores(model)

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

high_risk_processes(model)

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

stride_threats(model, opts \\ [])

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

This analysis answers the question: "What threats exist in my architecture?"

threat_summary(model)

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

This analysis answers the question: "How are threats distributed by category and severity?"

unencrypted_boundary_flows(model)

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

validate(model)

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

This analysis answers the question: "Is the threat model structurally sound?"