dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
Craig’s Threat Modeller
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A macOS application and a command line executable that build a threat model from
text files a team commits to git.
A project holds four source files for each system, and the application writes
a fifth:README 전문 보기 ↗A macOS application and a command line executable that build a threat model from text files a team commits to git.
A project holds four source files for each system, and the application writes a fifth:
| File | Who writes it | What it holds |
|---|---|---|
<name>.arch | a person | the architecture: technologies, zones, components and flows |
<name>.controls | the compiler writes it, then a person fills it in | the answer for every threat the architecture raises |
<name>.attacktree | a person | the routes through several components, and what each route raises |
<name>.governance | the compiler writes it, then a person fills it in | who carries each accepted risk, and who does each piece of planned work |
<name>.md | the compiler | the report |
library/<name>.lib | a team, and shared with other teams | technologies, threats and controls every system in the project reads |
A large system is split across files. threatmodel/<name>/arch/ holds its
architecture files, controls/ holds the answers beside them and
attacktree/ holds the trees; the files compose into one system with one
name, one namespace, one diagram and one report. One file holds the system
block and the rest hold blocks. threatmodeller split <name> moves a flat
system into that shape, and
the language guide states the rules.
The application draws the same files on a canvas. The executable reads them in continuous integration, so a pull request that adds a database and answers nothing fails the build.
Read the language guide for the syntax of all five source files: the lexical rules, the grammar, every block and attribute, the diagnostics and the canonical form.
threatmodel/payments.arch states the architecture:
system "Payments" {
catalogue = "v1.0.1"
zone "app" {
kind = "private"
network = "vpc"
reduces_risk_by = 30
component "api" {
technology = "aws-ec2"
name = "Application Server"
data = "confidential"
}
}
component "attacker" {
technology = "actor-attacker"
data = "public"
}
flow attacker -> api
}
threatmodel/payments.controls states the answers:
controls for "Payments" {
catalogue = "v1.0.1"
threat "t-credential-theft" on component "api" {
severity = "critical"
score = 90
control "Enforce MFA on all administrative access" {
status = "implemented"
note = "Okta, enforced group-wide"
}
compensating "Break-glass account watched by the SIEM" {
reduces_risk_by = 40
rationale = "Standing keys are gone; the one account left alerts on use."
}
}
threat "t-mitm" on flow "attacker->api" { }
}
A system is not only a network of services. It can also be a single host: the processes on it, the privilege each one runs at, and the calls between them.
kind: network, ipc, file, syscall, and more. A
local call raises no network-only threat, such as a man-in-the-middle attack.boundary: network (the default) or privilege. A
privilege zone raises the threats a library marks for that boundary.runs_as: user, admin, root,
system or kernel.mitigates
block. A security product lowers the score of the threat it answers,
wherever that threat is raised. This is a separate scoring stage from the
Pathway Mitigations panel below: a mitigates edge names one specific
protector and one specific threat, and it states its own status, either
adopted (the team has it, so it lowers the real score) or assumed (the
team plans it or believes it, so it only lowers the target score — see
"Scoring" below).Read the language guide for the full grammar of flow,
zone, runs_as and mitigates. Read
libraries/endpoint.lib for the technologies an
endpoint model draws from: the operating system, its shells, its security
products.
<project root>/
threatmodel/
library/
acme.lib
payments.arch
payments.controls
payments.md
internal-tools.arch
internal-tools.controls
The application reads <root>/threatmodel when that directory exists, and
<root> when it does not. A .arch file names a system. Its .controls file
and its .md file take the same stem beside it. The rule is in
ProjectConvention,
in one place, so the application and the executable cannot disagree about where
a file is.
Every .lib file under threatmodel/library/ is a shared element library, and
every system in the project reads every one of them. There is nothing to
declare.
The vendored catalogue holds the technologies every user shares. A
CustomTechnology holds one that belongs to a single diagram. A library
sits between them: a text file a team writes, commits and shares with other
teams.
library "acme" {
name = "Acme Platform"
technology "cribl-stream" {
name = "Cribl Stream"
category = "monitoring"
threats = ["pipeline-tamper"]
}
threat "pipeline-tamper" {
name = "Pipeline tampering"
severity = "high"
control "Sign pipeline configurations"
}
}
The label is a provider, and it prefixes every id the file declares, so two
teams can both define cribl-stream and neither clashes. A system names it:
component "ingest" {
technology = "acme-cribl-stream"
}
From that point a library entry and a catalogue entry are the same thing: the
palette shows an Acme Platform group, the sidebar raises
acme-pipeline-tamper, and the .controls file holds a stanza for it.
A library that does not load stops the project opening, rather than loading the libraries that do. The language guide states the grammar and every fault.
A library repository holds one or more .lib files at its root. Nothing else in
it is read, and nothing in it is ever run.
threatmodeller library add <repository> <tag> [<root>] # fetch and pin a library
threatmodeller library update [<label>] [<root>] # fetch again at the recorded tag
threatmodeller library remove <label> [<root>] # delete a library and its lock entry
threatmodeller library list [<root>] # say what this project holds
threatmodeller library verify [<root>] # check the files against the lock file
threatmodeller library outdated [<root>] # say which libraries have a newer tag
threatmodeller split <system> [<root>] # move a flat system into a directory
threatmodeller attack sync [<tag>] [<root>] # download and extract MITRE ATT&CK
threatmodeller attack verify [<root>] # check this machine against the lock file
threatmodeller actors list [--mitre] [<root>] # say what actors this project may face
threatmodeller cve sync [<root>] # fetch CVSS, EPSS and KEV for every CVE named
threatmodeller cve list [<root>] # say what cve.lock.json holds
add writes the files into threatmodel/library/ and writes
library.lock.json, which records the repository, the tag and the sha256 of
each file. A team commits both, so a pull request shows what changed.
{
"libraries" : {
"acme" : {
"files" : { "acme.lib" : "9f2c0b1e…" },
"repository" : "[email protected]:acme/threat-elements.git",
"tag" : "v2.1.0"
}
}
}
add, update and outdated run git as a child process, so a team keeps the
access it already has: an ssh-agent key, a ~/.ssh/config, a credential
helper, a self-hosted server, a private repository. This application holds no
credential. It reads none, stores none and prompts for none, and a repository
the user's own git cannot read is one it cannot read either. The message it
shows is git's own.
verify runs no child process and reaches no server, so it is the command a
continuous integration job runs:
threatmodeller library verify && threatmodeller check
remove refuses while a system names one of the library's technologies, and
says which systems. --force removes it anyway.
update fetches again at the tag the lock file already records and changes no
tag. To move version, run add with the new tag.
A component states the version it runs and the CVEs that version carries:
component "api" {
technology = "nginx"
version = "1.24.0"
cves = ["CVE-2023-44487", "CVE-2024-7347"]
}
cve sync reads every CVE the project names and fetches the CVSS base score
and summary from the NVD, the EPSS probability from FIRST, and whether the
CISA Known Exploited Vulnerabilities catalogue lists it. It writes the records
into threatmodel/cve.lock.json, with the date the EPSS feed states and the
version the KEV catalogue states, and a team commits that file: the score
reads it, so every machine scores the same. A second sync against an
unchanged upstream writes the same bytes. cve list prints what the lock
file holds and makes no network call. check warns for a CVE a component
states that the lock file does not hold, and does not fail for it.
A CVE the CISA KEV catalogue lists raises every threat on its component to
the commodity likelihood, the claim a faced commodity actor makes; a CVE the
catalogue does not list changes no score. The report writes
## Known vulnerabilities after ## Third parties: one row per CVE per
component with its CVSS, EPSS, KEV and a priority by the
CVE_Prioritizer quadrant, 1+
for a known exploited CVE and 1 to 4 by CVSS at or above 6.0 and EPSS at
or above 0.2. cve_cvss_threshold and cve_epss_threshold in
threatmodel/policy.hcl move the two thresholds. The JSON export writes the
same rows as knownVulnerabilities.
Look up CVEs… on a threat card runs
vulnx for the component the
threat is raised on, with the technology's name and the component's version,
and lists what came back with its CVSS, EPSS and KEV. Attach writes the
picked ids onto the component. The tool is the person's own, found on their
PATH; the application ships no tool and calls no network of its own for a
lookup. The application reads PATH from the person's interactive login
shell once at the first lookup, so a tool under ~/go/bin that ~/.zshrc
puts on PATH is found by a window started from the Finder, which inherits
launchd's PATH alone. A machine without it says
vulnx is not installed; install it with go install github.com/projectdiscovery/vulnx/cmd/vulnx@latest.
The command the application runs is vulnx search --json --limit 50 <product> [<version>].
WARNING: the NVD answers five requests in thirty seconds to a caller with no
key, so cve sync waits six seconds between NVD requests after the fifth.
THREATMODELLER_CVE_FEEDS=<directory> reads the three feeds from files in
that directory instead of the network, which is what scripts/cli-smoke.sh
does. The design is
the known vulnerabilities design.
Every threat carries two numbers. The residual score is the real state of
the system today. The target score — "if the assumptions hold" in the
report — is the residual score recalculated as though every assumed
mitigates edge were adopted. The two numbers differ only when a model
carries an assumed edge; the report prints the target score only then.
The resolver runs eight stages, in this fixed order, to reach the residual score:
severity_override block in the .controls file names this one threat on
this one source, or, failing that, an override set from the sidebar names
this threat on this element. A pathway threat uses the highest sensitivity among the
components it feeds directly, when that is higher than its own.reduces_risk_by percent, when the component sits in
a private zone.mitigates edges. The strongest mitigates edge that targets this
component and names this threat, counting only edges whose status is
adopted. An edge whose status is assumed is skipped at this stage, so
it never lowers the residual score; it lowers only the target score, at
the same stage run a second time over both adopted and assumed edges..controls file's likelihood block for this threat on
this source, or, failing that, the threat's own likelihood tier, multiplies
both the residual score and the target score. commodity (the default)
leaves the score unchanged; targeted multiplies by 0.6; insider
multiplies by 0.6; research multiplies by 0.25. A likelihood block can also raise a threat's tier
back to commodity.compensating block on the threat
reduces both the residual score and the target score..attacktree file raises the score of
the threat it names as its goal, by raises_risk_by scaled by the chain
factor. The chain takes the weakest open step, because a route is as
likely as its least likely step, and the goal's own likelihood is not in
the chain. A step is closed by an implemented control or a compensating
block and by nothing else. The stage raises the target score by the same
boost, and neither score passes the top of the scale. The design is
.Two answers at the same stage — two mitigates edges or two compensating
controls — give the stronger of the two, not
the sum. The report shows the score before controls and before compensation,
alongside the residual score. The threat card shows the score before pathway
mitigation.
A threat raised by a flow runs stages 1 to 4 and 6 to 8: it takes the source
component's upstream pathway mitigations, but no mitigates edge targets a
flow. A threat raised by a zone runs stages 1, 2, 3, 4, 6, 7 and 8: a zone
sits outside the connection graph, so it reads the mitigations inside itself
rather than upstream ones, and no mitigates edge targets it.
A likelihood block is evidence a person found, not a control a team built.
threatmodeller check uses it to close a threat when the evidence says the
threat is unlikely enough: a threat counts as answered when it has an
implemented control, a compensating block, or a likelihood block whose
current residual risk level (Low, Medium, High or Critical) ranks at
or below the project's risk tolerance.
The risk tolerance is a RiskLevel, read in this order: the --tolerance
flag, then the system's own risk_tolerance attribute in the .arch file,
then Low when neither is set. Low is strict: a likelihood block closes
only a threat whose residual score is already Low; it never closes a
Medium, High or Critical threat until the team raises the tolerance. A
severity_override block records an assessor's own severity decision for one
threat on one source, with a rationale and, optionally, sources; it wins over
a technology-wide override in stage 1 above.
Read the language guide for the full grammar of
severity_override, likelihood, risk_tolerance, assumption and
recommendation.
threatmodeller report and Generate Report write the same .md file, in
this order:
recommendation blocks on mitigates edges, the three
that remove the most risk; otherwise the three recommendation blocks
from the .controls file that answer the worst-scoring threats. The Do
first list is left out when the model holds neither. The section closes
with a count of the threats nobody has answered.mitigates edge), and a count by risk level.### Diagram legend subsection shows what each mark in the pictures means.recommendation block on an assumed mitigates edge, worst-removing
first: what it removes, how many threats it moves, the worst score before
and after, and what blocks it. Each action is measured alone against
today's posture; two actions that answer one threat do not remove the sum
of their leverage, so the numbers in the table do not add.The report opens with an executive summary, states the scale it scored against, and lists every threat above the project's risk tolerance before it lists anything else. The full threat register and the model inventory are appendices.
A section a model gives nothing to write about is left out, rather than printed empty.
threatmodeller compile [<root>] # writes or merges every .controls file
threatmodeller check [<root>] # says what has no answer
threatmodeller history [<root>] # says what the model scored at each commit
threatmodeller report [<root>] [-o <dir>] # writes every .md report and its diagrams
threatmodeller draw [<root>] [-o <dir>] # writes every diagram as SVG or PNG
threatmodeller export [<root>] [-o <dir>] # writes every model as data another program reads
threatmodeller list [<root>] # says what each system holds and what it scores
threatmodeller import terraform [<root>] # draws what a Terraform state holds
threatmodeller mcp [<root>] # serves the Model Context Protocol
threatmodeller lsp # speaks the Language Server Protocol
threatmodeller format [<root>] # rewrites every .arch, .attacktree and .lib file
threatmodeller library <operation> … # manages the shared element libraries
threatmodeller help # shows the usage text
report also writes one SVG for each threat in Top residual risk, beside
the report, and a Top residual risk in detail section that shows each one
with what has not answered it. A picture holds the element the threat is
raised on, everything one hop from it, and the zones those sit in, each zone
cut to what the picture still shows.
draw writes SVG by default. --svg and --png say which to write, and both
may be given. --mermaid, --dot and --d2 write the diagram as text
instead: <system>.mmd, <system>.dot and <system>.d2, each with the zones
as subgraphs, the components in their own shape and the flows stating their
kind. Text is what a wiki renders, a pipeline draws and a reviewer diffs, and
two runs on one model write the same bytes.
report --diagram mermaid writes the diagrams into the report itself, as
fenced mermaid blocks in place of the image links, so a GitHub wiki renders
the report with no image file beside it. SVG is written by the package itself, so every build writes it,
including the static Linux one. PNG needs a drawing engine, which only a macOS
build has; a Linux build says so rather than writing a broken file.
Options: -o <dir> writes the reports into that directory. --catalogue <dir>
reads the threat catalogue from that directory. --tolerance <level> sets the
risk tolerance check uses for a likelihood finding (see "Risk tolerance and
likelihood findings" above); <level> is low, medium, high or
critical. -q or --quiet says nothing about a file that did not change.
-f or --force removes a library a system still names. --format <name>
picks the shape check, compile and format write; see below.
threatmodeller export --format threatcl writes the model as
threatcl HCL, and the window writes the
same file through File ▸ Export as threatcl…. The file is written for
spec version 0.8.1, which release v0.6.9 of the threatcl binary
reads; the version is pinned in ExportModelAsThreatcl.specVersion.
Each named asset becomes an information_asset, each use case a usecase,
each exclusion and each assumption an exclusion, each third party a
third_party_dependency, each assessed threat a labelled threat with its
impacts, its stride, its information_asset_refs, a risk block and one
control block per control, and the diagram one data_flow_diagram_v2 with
the zones as trust_zone blocks, the components as process, data_store or
external_element by shape, and the flows as flow blocks with from, to
and protocol. A diagram block written in mermaid travels as a mermaid
block.
threatcl validate accepts the file. scripts/cli-smoke.sh runs the binary
when the machine holds it, CI installs it for the Linux job, and
ExportModelAsThreatclTests holds the whole file as a golden for a machine
without it.
threatmodeller report --template <file> renders the report through a
template, and template = "<path>" in threatmodel/policy.hcl states one for
every run. The flag wins over the file.
A template is a Markdown file with named slots and no logic:
---
banner: OFFICIAL — SENSITIVE
cover: true
cover_title: Payments threat model
cover_subtitle: Prepared for the security review board
---
# {{system_name}}
{{executive_summary}}
## Our own words
The board reads this section before the numbers.
{{findings}}
{{threat_register}}
A {{slot}} on a line of its own is replaced by that section, whole. Every
other line is copied out byte for byte. A slot the template omits is not
written; a slot named twice, and a slot that names no section, are each a
diagnostic that stops the run. The front matter states a classification banner
and a cover page, which the HTML report and the PDF both honour.
The slots and the rules are stated in the report template design. A project that names no template renders through the shape this application ships, which is the report as it has always read.
threatmodeller lsp speaks the Language Server Protocol over standard input
and output, framed the way the protocol states. Point an editor at it and the
four source languages stop being plain text:
flow, and the attributes of the block
the cursor sits in.threat stanza's score and level.flow names, and the technology or
threat a .lib file declares.threatmodeller format uses.semanticTokensProvider with the legend
keyword, string, number, comment, operator, variable, and
answers textDocument/semanticTokens/full for .arch, .controls,
.lib, .attacktree, .governance and policy.hcl. A block name, an
attribute name and a boolean read as a keyword; a block's label and the
component ids beside a flow arrow read as a variable, so a name shows
apart from a keyword. The colour comes from the same lexer the parsers
read, so a file mid-edit that does not parse still shows its colour, and a
comment the parse drops is coloured.Any editor that speaks the protocol reads this server. VS Code reads it through the extension below.
vscode/ holds a VS Code extension, written in TypeScript against
vscode-languageclient. The extension gives:
.arch, .controls, .lib,
.attacktree, .governance and policy.hcl, each with a comment and
bracket configuration.threatmodeller lsp and speaks
to it over standard input and output, so the diagnostics, the completion,
the hover, the go to definition, the formatting and the colour all come
from the executable.One setting names the executable:
| Setting | Default | What it states |
|---|---|---|
threatmodeller.path | threatmodeller | The path of the executable. The default reads the name from PATH. |
WARNING: the languages stop colouring when the executable is not there. The
extension then shows one message that names threatmodeller.path.
Build and test the extension with scripts/vscode-extension-smoke.sh, which
runs npm ci, npm run compile and npm test in vscode/. The test suite
starts a VS Code through @vscode/test-electron and states the file
associations, the setting default, the three commands, and that the client
sends initialize to the server.
The release workflow builds the extension from the tag, names the version
after the tag, and attaches threatmodeller-<tag>.vsix to the release.
Install that file with Extensions: Install from VSIX…. The workflow
publishes to the Visual Studio Marketplace only when the repository holds a
VSCE_PAT secret. The repository holds no such token today, so the
vsce publish step does not run.
threatmodeller mcp [<root>] serves the Model Context Protocol over standard
input and output, so an assistant reads the assessed model rather than
guessing at the files. Point a client at it:
{
"mcpServers": {
"threatmodeller": {
"command": "threatmodeller",
"args": ["mcp", "/path/to/the/project"]
}
}
}
The tools: list_systems, read_system (the JSON of the export schema),
check, compile, describe_threat, describe_technology,
describe_control and open_threats_above. Each runs the verb a person runs,
so an assistant and a person read the same answer. Every source file the
project holds is offered as a read-only resource.
The server writes nothing unless --allow-writes is given. With the flag it
offers two more tools: answer_control answers one control on one threat, and
set_likelihood writes a likelihood finding, each writing the .controls
file the application itself writes. Without the flag, compile says what it
would write and writes nothing.
A team whose infrastructure is Terraform already holds the components, the
networks and the data stores as state. threatmodeller import terraform reads
the JSON of terraform show -json on standard input and writes or updates one
.arch file:
terraform show -json | threatmodeller import terraform .
Every element the import writes states source = "terraform". A second import
over the same state writes the same bytes. A resource gone from the state
removes the element it made and says so; an element a person wrote states no
source and is never removed or changed, and what a person wrote on an
imported element — its classification, its assets, its name — stays. A
resource type with no mapping is counted and named once, and the import still
writes everything it could map.
The mapping from resource types to technologies, from VPCs and subnets to zones, from security group rules to flows, and the list of what an import may not decide, are stated in the Terraform import design.
threatmodeller list writes one row per system: its name, its file, its owner,
how many components, zones and flows it holds, how many threats it raises, how
many of those nobody has answered, how many carry an accepted risk, the worst
score and its level, the catalogue tag and when the model was last read again.
threatmodeller list . # every column, a header, file order
threatmodeller list . --fields name,unanswered,worst # these columns, in this order
threatmodeller list . --sort unanswered --no-header # worst first, no header
threatmodeller list . --json # the same rows as an array
A system whose files do not parse takes a row holding its name, its file and
the word unparsed, and the verb still exits 0: check is the verb that
fails, and a list that drops a system hides it.
threatmodeller export writes one file per system, holding the system's
facts, its zones, components, flows, assets, third parties, assumptions,
scope, every threat with its score, its level, its answers and its controls,
the recommendations, the leverage, the accepted risks and the summary counts.
threatmodeller export . # one <system>.json beside each .arch file
threatmodeller export . --format otm # one <system>.otm.json, the Open Threat Model shape
threatmodeller export . --stdout # one system to standard output
The shape is stated in docs/threatmodel-export.schema.json,
and each file states the schema version it was written against. Keys are
written in alphabetical order, so two exports of one model are the same bytes
and a file committed to a repository changes only when the model changes.
The open critical threats, from a pipeline:
threatmodeller export . --stdout | jq -r '.threats[] | select(.isOpen and .riskLevel == "critical") | "\(.riskScore)\t\(.name)\t\(.sourceName)"'
--format otm writes an Open Threat Model file instead. That schema carries a
project, trust zones, components, dataflows, threats and mitigations, and
docs/OTM-MAPPING.md states what this model becomes in
it and what it leaves behind.
The window writes the same file: File → Export as JSON… runs the same use case.
A .controls file lets a person write status = "accepted" and move on. That
records that somebody accepted the risk, and nobody's name and no date. The
.governance file beside it is where the decision lives: who carries the risk,
when they took it, and when they read it again. It also states who does each
recommendation and each action, how big the work is and when it is due.
threatmodeller compile writes the file and every stanza in it. A person fills
in the fields and commits it. threatmodeller check exits 1 for an accepted
risk with no governance entry, with no owner, with no review date, or with a
review date that has passed. Planned work fails nothing: a plan with no owner
is a gap in a plan, and the report prints it.
WARNING: a project that accepts a risk today and holds no .governance file
fails check the first time it runs after this change. The fix is two steps:
threatmodeller compile, which writes the file and a stanza for every
accepted control.owner and review_by in each stanza, and commit the file.The report writes ## Accepted risks after ## Recommendations, one row per
accepted control, governed or not, and the executive summary states how many
are past their review date.
A report states the posture of one day. threatmodeller history states the
direction:
2026-09-14 a1b2c3d Craig total 184 worst 12 critical 2 high 5 accepted 1 trees 1 v1.0.1
2026-09-07 9f8e7d6 Craig did not parse
The history is your git history. Every commit that touched a threat model file holds the files of that day, the compile is deterministic, so the score at that commit is recoverable by reading them. Nothing is stored, nothing is checked out, and neither the working tree nor the index is touched.
--commits <n> bounds the sample, newest first, and defaults to 50. A commit
whose files do not parse keeps its row and says so, because a zero would read
as "no risk".
report writes ## Risk over time with a graph beside it, and ## What changed between the previous sampled commit and the working tree: threats
raised and gone, controls whose status moved, risks newly accepted, review
dates moved, and the score of each element. The executive summary states the
direction in one sentence. --commits 0 turns all of it off. The application
reads the same rows from the History button, at your request and never on open.
What reading it costs is measured in docs/TESTING.md.
threatmodeller check fails a build for an unanswered threat, a stale answer,
a stale tree and an accepted risk nobody governs. threatmodel/policy.hcl is
where a project states the rest:
policy {
max_open_at_level = "high"
accepted_requires_owner = true
accepted_requires_review_by = true
implemented_requires_evidence_above = "high"
restricted_data_stays_out_of_public_zones = true
assumptions_require_owner = true
system_requires_owner = true
}
| Rule | What it asks |
|---|---|
max_open_at_level | no threat at that level or worse is unanswered |
accepted_requires_owner | every accepted risk names an owner |
accepted_requires_review_by | every accepted risk names a review date |
implemented_requires_evidence_above | every implemented control on a threat at that level or worse, before its controls, states an evidence tier |
restricted_data_stays_out_of_public_zones | no component holding restricted data sits in a public zone, or outside every zone |
assumptions_require_owner | every assumption names an owner |
system_requires_owner | the .arch file states owner |
The set is fixed names rather than an expression language, so a rule is one a
team cannot mistype, the report can explain it, and it survives a change to the
model. A breach prints one line naming the rule and exits 1. A project with no
policy file checks exactly as it did. The report writes ## Policy after the
executive summary, listing every rule and whether the system keeps it.
A control block takes status = "implemented", and four teams write that
word and mean four things. evidence says which: asserted, documented,
configured, tested or audited, weakest first, which is the order of what
a reader can check for themselves. reference says where the proof is and
verified_on says when somebody last checked. A compensating block takes the
same three.
A tier moves no score. The control is either in place or it is not; the tier says how well a reader can check that claim. The report names the tier beside each implemented control and the executive summary counts the ones that state none.
A project that wants more states it in its .arch file:
system "Payments" {
requires_evidence_above = "high"
}
threatmodeller check then exits 1 for an implemented control on a threat
whose risk level before its controls is that level or worse and that states
no tier. The level is read before the controls, because reading it after would
let the controls lower the score far enough to exempt themselves. A project
that states nothing fails nothing.
--format <plain|github|json> says which shape check, compile and format
write. It defaults to plain, which is the lines a person reads in a terminal.
The exit codes are the same whichever shape is asked for.
--format github writes GitHub Actions workflow commands, so a pull request
shows the failure beside the line it names rather than a red check with no word
on it:
::error file=threatmodel/payments.controls,line=14,col=1::dos-attack on component "api" (Low) has no answer
::warning file=threatmodel/payments.controls,line=1,col=1::a severity_override names a severity the catalogue does not hold
An unanswered threat names the line of its threat stanza when the .controls
file holds one, and line 1 when it does not.
--format json writes one object, and nothing else, so any other tool reads
one document:
{
"messages" : [],
"systems" : [
{
"diagnostics" : [],
"name" : "payments",
"stale" : ["sql-injection@component:gone"],
"staleTrees" : [],
"tolerance" : "low",
"unanswered" : [
{
"file" : "threatmodel/payments.controls",
"line" : 14,
"riskLevel" : "Low",
"sourceId" : "api",
"sourceKind" : "component",
"threatId" : "dos-attack"
}
]
}
]
}
messages holds what the run said about the project rather than about one
system: a catalogue that would not load, a library warning, a directory that
holds no .arch file.
<root> is the project root, and defaults to the working directory.
Exit codes: 0 success; 1 a threat is unanswered, a stale answer remains, a
library file does not match the lock file, a library a system names was not
removed, or a library has a newer tag; 2 a file did not parse; 3 a file
could not be read or written; 4 a library could not be fetched.
A diagnostic prints as
threatmodel/payments.arch:12:5: error: no technology "aws-ec3" in catalogue v1.0.1,
which an editor and a build log both read.
Three ways, in the order most people want them.
From the application. threatmodeller ▸ Install Command Line Tool… writes a
link in ~/.local/bin, which is your own directory, so it asks for no password
and touches nothing outside your home. The window says whether that directory is
on your PATH and, when it is not, gives you the one line to add to ~/.zshrc.
The same window uninstalls it, and says so when the link points at a copy of the
application you have moved.
From a release. The tarball holds the executable with the catalogue beside it, and needs no flag:
tar xzf threatmodeller-cli-<version>-macos.tar.gz
cd threatmodeller-<version>
./threatmodeller check /path/to/your/project
ln -sf "$PWD/threatmodeller" ~/.local/bin/threatmodeller
From this repository. A link to the build output keeps working after every rebuild:
cd ThreatModelKit
swift build -c release --product threatmodeller-cli
mkdir -p ~/.local/bin
ln -sf "$(swift build -c release --product threatmodeller-cli --show-bin-path)/threatmodeller-cli" \
~/.local/bin/threatmodeller
If ~/.local/bin is not on your PATH, add this to ~/.zshrc and open a new
terminal:
export PATH="$HOME/.local/bin:$PATH"
The executable finds its catalogue in the directory it sits in, and resolves its
own path through a symbolic link first, which is why a link from PATH works
from anywhere.
.arch file and merges the
answers into its .controls file..md file described in "The report" above. A
report is an artefact, so a synchronise does not write it..md report, and Export as threatcl…, Export
as PDF… and Export as Image… write the architecture or the canvas in
those formats. Only the Markdown export is the project's report; the other
three exist only from this menu.threatmodeller format are the same file. The shape is stated in
the attack tree stage design.threatmodeller compile outside the application is not known to the window,
so the item stays off until the window writes one.<path>:<line>:<column>: <message>, the shape the
command line prints.The .threatmodel document and its window still work. Export Architecture… is
how a document becomes a project. It is one way: a project does not become a
document. File ▸ Open Example… (Cmd+Shift+O) opens the sample browser for
this document window; opening an example replaces the model on the canvas,
and undo takes it back.
.arch, .controls, .lib, .attacktree, .governance and
policy.hcl.threatmodeller export --format json writes.docs/superpowers/specs/ — every design spec, and the
carry-forward note for each milestone.docs/superpowers/plans/ — the implementation plans.| Path | What it holds |
|---|---|
ThreatModelKit/Sources/ArchitectureDSL | the lexer, both parsers and both writers |
ThreatModelKit/Sources/ThreatModelKit/architecture | the project convention and the use cases over it |
ThreatModelKit/Sources/CommandLineApplication | the verbs and their exit codes |
GitLibraryFetcher.swift | the one place this application runs git |
CommandLineTool.swift | putting threatmodeller on the user's PATH |
scripts/embed-cli.sh | putting the executable inside the application bundle |
threatmodeller/project | the project window, the workflow bar, the notice strip and the Libraries sheet |
scripts/update-catalogue.sh | refreshes the vendored threat catalogue |
recommendation.controlscheckassumption block from the .arch file, then a
### Assumed mitigations subsection listing every mitigates edge whose
status is assumed.mitigates edge,
and its controls.#### <zone name> subsection for each zone,
listing the network zone, network type, boundary, risk reduction, and
components.Cmd+=), Zoom Out (Cmd+-), Actual Size
(Cmd+0), Zoom to Fit (Cmd+9), Zoom to Selection, and the four stages
(Cmd+1 to Cmd+4). The floating panel states the zoom, and a click on it
opens the same items.Shift+Cmd+L) lays the drawn model out again and
moves every component and every zone to the result, as one undoable change.
Lay Out Selection (Cmd+Option+L) does the same for what is selected and
moves nothing else. The layout runs off the main actor, and the toolbar says
what it is doing while it runs.Shift+Cmd+C) puts the diagram on the clipboard as
PNG and as PDF. With elements selected it copies those elements and the
flows between them, cropped to their bounds.Cmd+Option+]) and Send Zone to Back (Cmd+Option+[) set it, and the
.arch file writes the zones in that order.<directory>/library/<name>.lib. Every system in the project then reads
it, and another project vendors it with threatmodeller library add. The
technology takes the identifier the library mints, and every component that
named the old one names the new one.index.json, read only when a person presses the button. A machine with no
network says so, and adding by repository and tag still works. See
the library index design.~/Library/Application Support/threatmodeller/attack on macOS and in
$XDG_DATA_HOME/threatmodeller/attack on Linux, and the project commits only
mitre.lock.json. threatmodeller attack sync and attack verify do the
same from the command line, and actors list --mitre says which groups touch
this model. See
the synchronise design.git and
inherits their ssh-agent. Update and Remove act on the selected row, and
Remove asks again when a system still names the library. Check for updates
is the one control that reaches a server without being asked for a change, and
a person presses it: nothing checks on its own. A fetch runs off the main
thread, and Cancel Fetch stops one that is waiting; a cancelled fetch
writes no library file and no lock entry. Check for updates compares tags
as versions, so v10 is newer than v9, and a pre-release is offered only
to a project already running one..arch file and the .controls file when the model changes, half a second
after the changes stop, and it redraws the diagram when a file changes on
disk. When Auto Sync is off, nothing is written until the user presses
Synchronise, and the notice strip says the files changed on disk.line:column message.