Threatmodel block
Each threatcl hcl file has to include one more unique threatmodel blocks.
threatmodel attributes
| Attribute | Type | Req’d | Description |
|---|---|---|---|
| NAME | label string | Specifies the name of the threat model | |
| author | string | Specifies the author of the threat model | |
| description | string | Describes the threat model | |
| link | string | An href to other documentation | |
| diagram_link | string | An href to a diagram. (see diagram_link) | |
| repository | array [string] | Source code repositories this model covers. See repository | |
| id | string | A stable, identifier-safe handle for this model. See id | |
| extends | string | Inherit content from another threat model. See extends | |
| created_at | integer | Creation UNIX timestamp | |
| updated_at | integer | Updated UNIX timestamp | |
| attributes | block | See attributes | |
| additional_attributes | block | See additional_attributes | |
| usecase | block | See usecase | |
| exclusion | block | See exclusion | |
| third_party_dependency | block | See third_party_dependency | |
| information_asset | block | See information_asset | |
| threat | block | See threat | |
| data_flow_diagram_v2 | block | See data_flow_diagram_v2 | |
| mermaid | block | See mermaid | |
| including | string | See including | |
| imports | array [string] | See imports |
diagram_link
If it ends in .jpg or .png then it will be embedded in the resultant dashboard. If this isn’t set, but there is a data flow diagram, this will be automatically generated and included when running threatcl dashboard
repository
An optional list of source code repositories that this threat model covers. Each entry is a full URL, scheme included, so a single model can point at more than one repository.
repository = [ "https://github.com/example/api", "https://github.com/example/worker",]Repositories render as a ## Repositories section in markdown output, are inherited by a threat model that pulls this one in with including, and are exported under project.attributes.repository in OTM.
id
An optional, identifier-safe handle for the threat model. Names are arbitrary strings, so they can’t be used in dotted references. An id gives the model a stable address that survives a rename.
threatmodel "Tower of London" { id = "tower_of_london" author = "@xntrik"}Each segment must be lowercase letters, digits or underscores, and start with a letter. Ids may be dot-separated to namespace a model beneath a parent (buildings.tower) and must be unique across every file parsed together. When you don’t declare one, an identifier is derived from the name ("Tower of London" → tower_of_london); only a declared id is rename-stable.
See Multiple Files for namespacing rules, and Invariants for where these addresses are used.
extends
An optional attribute naming another threat model’s declared id. The extending model inherits that model’s threat, information_asset, usecase, exclusion and third_party_dependency blocks, plus its attributes block when the child declares none.
threatmodel "Base controls" { id = "base" author = "@xntrik"
threat "Credential stuffing" { description = "Attackers replay leaked credentials" }}
threatmodel "Payments" { id = "base.payments" extends = "base" author = "@xntrik"}Same-named items declared on the child win over the inherited ones. Scalars (description, link, and friends), data flow diagrams and mermaid diagrams are deliberately not inherited, they stay per-model. Chains resolve parent-first, and both cycles and unknown targets are parse errors.
See Multiple Files for using extends across separate files.
attributes
The attributes block is optional, but recommended. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| new_initiative | boolean | Whether this is a new project | |
| internet_facing | boolean | Whether this system is exposed to the Internet | |
| initiative_size | string | Undefined, Small, Medium, Large |
additional_attributes
Your threatmodel may include multiple additional_attributes blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the attribute | |
| value | string | The value of the attribute |
usecase
Your threatmodel may include multiple usecase blocks. These don’t have labels or identifiers. It includes the following attribute.
| Attribute | Type | Required | Description |
|---|---|---|---|
| description | string | A description of the system’s use case |
exclusion
Your threatmodel may include multiple exclusion blocks. These don’t have labels or identifiers. It includes the following attribute.
| Attribute | Type | Required | Description |
|---|---|---|---|
| description | string | Define of out-of-scope exclusions of the threatmodel |
third_party_dependency
Your threatmodel may include multiple third_party_dependency blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the 3rd party dep | |
| description | string | A description of the 3rd party dep | |
| uptime_dependency | string | none, degraded, hard, operational | |
| saas | boolean | Is this a SaaS dep? | |
| paying_customer | boolean | Are we paying for it? | |
| open_source | boolean | Is this OSS? | |
| infrastructure | boolean | Is this Infra? | |
| uptime_notes | string | Notes about what happens if unavailable |
information_asset
Your threatmodel may include multiple information_asset blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the asset | |
| description | string | Describe the asset | |
| information_classification | string | Public, Confidential, Restricted | |
| source | string | Where was block sourced from i.e. terraform | |
| ref | string | A reference to a library item. See ref |
threat
Your threatmodel may include multiple threat blocks, with unique labels. Two threats in the same threat model can’t share a name. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the threat | |
| description | string | Describe the threat | |
| impacts | array [string] | Confidentiality, Integrity, Availability | |
| stride | array [string] | Spoofing,Tampering, Repudiation, Info Disclosure, Denial of Service, Elevation of Privilege | |
| information_asset_refs | array [string] | Refer to information_asset blocks. See Referring to Elements | |
| control | block | See control | |
| control_imports | array [string] | Allows you to import full expanded control blocks. See imports | |
| risk | block | Rate this threat’s likelihood and impact. See risk | |
| ref | string | A reference to a library item. See ref |
risk
Each threat may include a single optional risk block, rating the threat before controls are considered. threatcl derives a severity and a 0-100 score from that rating, then a residual view that factors in the risk_reduction of every implemented control.
threat "Credential stuffing" { description = "Attackers replay leaked credentials against the login endpoint"
risk { likelihood = "high" impact = "very_high" rationale = "Credentials for this user base appear in known breach corpora" }
control "MFA" { description = "Multi-factor authentication on all accounts" implemented = true risk_reduction = 80 }}| Attribute | Type | Required | Description |
|---|---|---|---|
| likelihood | string | very_low, low, medium, high, very_high | |
| impact | string | very_low, low, medium, high, very_high | |
| severity | string | Override the computed severity: info, low, medium, high, critical | |
| rationale | string | Free text explaining the rating |
likelihood and impact are normalised, so "Very High", "very-high" and "very_high" all mean the same thing.
Severity matrix
Unless you set severity explicitly, it’s computed from likelihood x impact:
| Likelihood \ Impact | very_low | low | medium | high | very_high |
|---|---|---|---|---|---|
| very_high | low | medium | high | critical | critical |
| high | low | medium | high | high | critical |
| medium | low | low | medium | high | high |
| low | info | low | low | medium | high |
| very_low | info | info | low | low | medium |
Scores and residual risk
Each level also carries a numeric weight (very_low 10, low 30, medium 50, high 75, very_high 95) and the inherent score is those two weights multiplied as percentages, back onto a 0-100 scale. A high likelihood with a very_high impact scores 0.75 x 0.95, or 71.2.
The residual score applies the risk_reduction of every implemented control to that inherent score, with diminishing returns: two controls reducing risk by 50% each leave 25% of the risk, not 0%. Unimplemented controls don’t reduce anything. The residual score maps onto a band of its own: 75+ is critical, 50+ high, 25+ medium, 10+ low, below that info.
The threat above renders in threatcl view and threatcl dashboard as:
> Risk: Likelihood _high_ × Impact _very_high_ → Inherent Severity **critical** (score 71.2)>> Credentials for this user base appear in known breach corpora
> Residual Risk (after implemented controls): score 14.2 (**low**, 80% reduced)Risk ratings are also exposed by the GraphQL API, and likelihood/impact map onto a threat’s risk object when exporting to OTM.
control
Your threat blocks may include multiple control blocks, with unique labels. Two controls within the same threat can’t share a name. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the control | |
| description | string | Describe the control | |
| implemented | boolean | Is it implemented? | |
| implementation_notes | string | Describe the implementation | |
| risk_reducation | integer | How much this control reduces risk | |
| ref | string | A reference to a library item. See ref | |
| attribute | block | See attribute |
attribute
Your expanded_control blocks may include multiple attribute blocks, with unique labels. It includes the following attribute.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The name of the attribute | |
| value | string | The value of the attribute |
data_flow_diagram_v2
Your threatmodel may include multiple data_flow_diagram_v2 blocks, with unique labels. It includes the following attributes.
Unlike other blocks, when you define a process, data_store, or external_element they must have a globally unique label, for that particular dfd. For example, you can’t have a process and data_store called “foo”.
Elements refer to each other by label; a flow’s from and to, a data store’s information_asset, an element’s trust_zone. Each of those also accepts a slug or a dotted expression, such as process.web_app. See Referring to Elements.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | dfd name | |
| process | block | See process | |
| data_store | block | See data_store | |
| external_element | block | See external_element | |
| trust_zone | block | See trust_zone | |
| flow | block | See flow |
process
Your data_flow_diagram_v2 block may include multiple process blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | process name | |
| trust_zone | string | defines a trust boundary / zone |
data_store
Your data_flow_diagram_v2 block may include multiple data_store blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | data store name | |
| trust_zone | string | defines a trust boundary / zone | |
| information_asset | string | refers back to a previous information_asset block ref |
external_element
Your data_flow_diagram_v2 block may include multiple external_element blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | external element name | |
| trust_zone | string | defines a trust boundary / zone |
trust_zone
Your data_flow_diagram_v2 block may include multiple trust_zone blocks, with unique labels. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | trust zone name | |
| process | block | see process | |
| data_store | block | see data_store | |
| external_element | block | see external_element |
flow
Your data_flow_diagram_v2 block may include multiple flow blocks. A flow must have a label, but it doesn’t have to be unique. But, the from and to attributes must be unique. It includes the following attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | flow title | |
| from | string | refer to a process, external_element or data_store | |
| to | string | refer to a process, external_element or data_store | |
| protocol | string | an optional protocol |
mermaid
Your threatmodel may include multiple mermaid blocks, with unique labels, for embedding free-form mermaid diagrams. Unlike data_flow_diagram_v2, which threatcl draws for you from elements and flows. A mermaid block carries raw mermaid source verbatim, so any diagram type works.
mermaid "Login sequence" { description = "How a user authenticates"
content = <<-EOT sequenceDiagram User->>App: credentials App->>Auth: verify Auth-->>App: token EOT}| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The diagram’s title | |
| content | string | The raw mermaid source. A heredoc is the idiomatic form | |
| description | string | Describe the diagram |
There’s no type or engine attribute, mermaid infers the diagram type from the first line of content.
These render into the markdown produced by threatcl dashboard and threatcl view, and are carried through the json, hcl and OTM exports. To pull the raw source back out (to pipe into a renderer such as mmdc, for instance) use the threatcl mermaid command.
including
Each threatmodel may optionally include an including attribute string that refers to a single other threatcl hcl file.
In this file must be a single, valid threatmodel block.
This included threatmodel will form the base of this new threatmodel. Any duplicate fields in the parent threat model will overwrite those from the included.
See External HCL Files for more information on how to refer to external files.
imports
Each threatmodel may optionally include an imports attribute with an array of string values that refer to other hcl files.
These files are used to centrally define various attributes that you can then refer to in your threatmodel. Currently we only support the definition of “control” components.
An example import file may look like:
component "control" "control_name" { description = "A control that can be used in multiple places"}
component "control" "authentication_control" { description = "Multi-factor authentication required" implemented = true implementation_notes = "Using TOTP for all admin accounts" risk_reduction = 80
attribute "category" { value = "Authentication" }
attribute "framework" { value = "NIST" }}To use these imports, you reference to the string value like this:
threatmodel "tm" { author = "@xntrik" description = "A threat model"
imports = ["controls.hcl"]
threat { description = "Something bad"
expanded_control "control" { description = import.control.control_name.description } }}You can also use string interpolation as well, so for instance:
description = "Some text that and ${import.control.control_name.description}"Technically you can use these import values similar to Variables
To use the more complex, multi-attribute control component, such as authentication_control above, you use the control_imports string array inside a threat block. For example, to import the authentication_control from above:
threat { description = "Authentication is bypassed"
control_imports = ["import.control.authentication_control"]}When this is Viewed, or Exported, that threat will include that external Expanded Control block
threat { description = "Authentication is bypassed"
control "authentication_control" { implemented = true description = "Multi-factor authentication required" implementation_notes = "Using TOTP for all admin accounts" risk_reduction = 80
attribute "category" { value = "Authentication" } attribute "framework" { value = "NIST" } } }See External HCL Files for more information on how to refer to external files.
ref
The information_asset, threat and control blocks each accept an optional ref attribute: a reference to an item in a shared library, rather than a copy of it.
threat "SQL Injection" { description = "Untrusted input reaches the database driver" ref = "T-SQLI"
control "Parameterized Queries" { description = "All queries use bound parameters" ref = "C-PQUERY" implemented = true }}To threatcl itself a ref is just a string, and the file stays valid without a library to resolve it against. Threatcl Cloud uses these to link a model to its organization’s threat, control and information asset libraries. threatcl cloud view and threatcl cloud export resolve each ref and fill in the library’s description, STRIDE, implementation guidance, risk reduction and classification. Values you set locally always win over the library’s.
backend
A backend block sits at the top level of a file, outside your threatmodel blocks, and links the file to Threatcl Cloud.
backend "threatcl-cloud" { organization = "acme" threatmodel = "my-app"}| Attribute | Type | Required | Description |
|---|---|---|---|
| NAME | label string | The backend’s name, i.e. threatcl-cloud | |
| organization | string | The organization this model belongs to | |
| threatmodel | string | The cloud threat model slug | |
| project | string | An optional project |
A file may declare at most one backend block.