Build your first threat model
This is the shortest path to something real: one command that reads a repository’s annotations and produces its threat model, a dashboard, and a SARIF export. It sends no traffic, needs no target, and needs no coding agent.
By the end you can read the three numbers that matter and, more usefully, see which threats a probe would never have been able to test.
Before you start
Section titled “Before you start”bravosandguardlinkon yourPATH. See Install Bravos.- A repository that already carries guardlink annotations. If yours does not,
bravos annotatewrites them, or annotate it with guardlink by hand first.
Build the model
Section titled “Build the model”-
From the repository root:
Terminal window bravos model .run 20260910-004305-demo · branch bravos/run-20260910-004305-demo11 annotations · 3 exposures · 4 threats trackedreport /Users/you/.bravos/runs/20260910-004305-demo/rounds/1/model-report/threat-model.mdreport (json) /Users/you/.bravos/runs/20260910-004305-demo/rounds/1/model-report/threat-model.jsondashboard /Users/you/.bravos/runs/20260910-004305-demo/rounds/1/model-report/threat-dashboard.htmlsarif /Users/you/.bravos/runs/20260910-004305-demo/rounds/1/findings.sarifMODEL READY — open /Users/you/.bravos/runs/20260910-004305-demo/rounds/1/model-report/threat-dashboard.html→ see it in the Bravos dashboard: bravos dashboardIt takes about a second. The run id is the timestamp plus the directory name.
-
Check what it did to your repository.
Terminal window git status --shortgit branch --show-currentbravos/run-20260910-004305-demoYour working tree is untouched:
git statusprints exactly what it printed before. What changed is the current branch. Bravos checked outbravos/run-<run-id>so that anything a later phase writes lands somewhere revertible.bravos model . --no-checkpointskips the branch, and either way the model tier commits nothing. -
Read the three numbers.
11 annotations · 3 exposures · 4 threats trackedNumber Means annotations every guardlink annotation in the repository: assets, threats, flows, mitigations, and exposures together exposures the @exposessubset that survives into the export: a threat hypothesised against an asset at a linethreats tracked rows in this run’s ledger Threats tracked exceeds exposures here because the ledger also carries the exposures guardlink’s export drops. The next step is where those show up.
Read what the model cannot see
Section titled “Read what the model cannot see”bravos model tells you what is there. bravos inspect tells you where the
view is lossy, which is the part worth your attention.
bravos inspect . /Users/you/demo guardlink 2.0.0 · cxg pins 6 files sarif: /Users/you/demo/whitebox/findings.sarif
SUMMARY exposures 3 (3 critical) confirmed 0 unique pairs 2 ← what `guardlink diff` can distinguish annotations 11 assets 2 threats 3 flows 0
EXPOSURES (3) critical #orders-dao #sqli app/data/orders-dao.js:4 critical #orders-dao #sqli app/data/orders-dao.js:8 critical #orders #idor app/routes/orders.js:7
PAIR COLLISIONS (1 groups, 1 findings maskable) Distinct exposures sharing one asset::threat pair. `guardlink diff` keys on that pair alone, so discovering another member of a group reports 0 added — a false dry round. orders-dao::sqli ×2 critical app/data/orders-dao.js:4 critical app/data/orders-dao.js:8
SUPPRESSED FROM EXPORT (1) A @mitigates or @accepts anywhere in the repo removes every exposure sharing that pair from the SARIF export. cxg never sees these and cannot test them. high orders::xss ×1 via @mitigates
REACHABILITY file-anchored 0 usable for goal synthesis asset-guessed 0 route inferred, often wrong no route 3 needs agent enrichment
CXG CORRELATION hypotheses 3 correlated 3/3 okFour things to take from it.
Unique pairs, against exposures. guardlink identifies an exposure by its
asset and threat alone, so three findings collapse into two identities. A
second, different weakness in a pair that is already known cannot be
distinguished from the first, and guardlink diff reports nothing added.
Pair collisions. The block names the group it happened in. Bravos keys its own ledger on asset, threat, file and a hash of the message instead, which is what lets a round say what it actually added.
Suppressed from export. One @mitigates anywhere removes every exposure
sharing that asset and threat pair. A control that is 90% correct is more
dangerous than none, because it ends the investigation silently. The
mitigation-audit lens exists to attack exactly
these.
Reachability and correlation. No annotation here carries a route, so every
goal will need an agent to work out what to send, which is the slow part of a
verify run. Correlation short of 3/3 means findings would arrive that cannot
be traced back to an exposure, and an unattributable finding is never written
back.
Read the ledger
Section titled “Read the ledger”Every threat is in it already, all of them unverified:
bravos ledger 4 threats tracked · 0 settled · 3 outstanding
UNVERIFIED (3) critical #orders-dao #sqli app/data/orders-dao.js:8 critical #orders-dao #sqli app/data/orders-dao.js:4 critical #orders #idor app/routes/orders.js:7
SUPPRESSED (1) high orders xss app/routes/orders.js:13The suppressed row is the point. It is counted, not absent: something a probe cannot reach is a gap in coverage, and a tool that dropped it would report the run as more complete than it was.
Open the dashboard
Section titled “Open the dashboard”bravos dashboardA local page over every run on this machine, on http://127.0.0.1:8787 by
default. It is read-only: it displays runs and hands you commands, and nothing
in it starts a run. --port moves it and --no-open stops it launching a
browser. The command does not return; stop it with Ctrl-C.
What you have
Section titled “What you have”- A threat model report and dashboard, under
$BRAVOS_HOME/runs/<run-id>/. - A SARIF export, the file cxg consumes.
- A ledger of every tracked threat, all of it still unverified.
Every threat in that ledger is a hypothesis. Nothing has been tested. Making them true or false is the verify tier’s job, and it sends real exploit traffic:
- Run the loop unattended: intake, a
plan you approve, then
bravos auto. - Authorization and blast radius: read this before pointing it at anything.

