Skip to content

What Bravos is made of

Two engines sit under Bravos, and neither is hidden.

  • guardlink holds the threat model. It is the annotation language the model is written in, the parser that resolves it, and the SARIF export a probe is generated from.
  • cert-x-gen executes the probes. Every request Bravos sends, it sends through cxg pentest.

Both are documented on this site, both work on their own, and wiring them together is one file: the SARIF that passes between them.

So the useful question is not “why Bravos” but “what does the handoff not answer”. This page is that list, and it starts with the case for not using it at all.

  • You want one answer about one thing. A single exposure, a single probe. cxg pentest against a hand-written scope is faster and you can read every step of it.
  • You do not have identities. Much of what Bravos adds is about who is attacking, and a target with one login or none gets little of it.
  • You cannot let an agent edit the repository. Bravos’s annotate and write-back phases are agent-driven by design. If that is not acceptable, guardlink’s own workflow annotates without one.
  • You need to understand the tools before you trust the loop. Run both by hand first. The loop is easier to read once you have seen its parts fail.

guardlink identifies an exposure by asset::threat. Two different weaknesses in the same pair are one identity, so guardlink diff cannot see the second one and the round reads “nothing added”.

bravos inspect reports the collision rather than leaving you to discover it. On a small repository with three exposures across two pairs:

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

Bravos keys its own ledger on asset, threat, file and a hash of the message, which is what lets a round say what it actually added.

One @mitigates or @accepts anywhere removes every exposure sharing its pair from the export. cxg never sees them, so no probe is generated, so nothing can confirm or refute them. Nothing warns you.

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

Bravos seeds those into the ledger as suppressed so they are counted rather than absent, and the mitigation-audit lens restates them into the export so the control itself gets attacked. A control that is 90% correct is more dangerous than none, because it ends the investigation silently.

cxg exits 0 for a clean scan, for a scan that skipped every probe for lack of identities, and for a scan aborted partway. A scan that stopped early writes a report whose findings list is indistinguishable from a clean one’s.

Bravos reads the report body as well as the exit code, and the three places cxg says its own scan did not finish. Only a conclusion may move a verdict, so a threat whose probe never reached the target stays in the backlog instead of being counted as fine. That distinction is the whole of Verdicts and the ledger.

cxg’s own login flow assumes a shape many real targets do not have. Bravos captures sessions with its own headless login, driven by an environment recipe: one target authenticates at POST /login with {userName, password}, another at POST /api/auth with a JWT in the body and a CSRF token in a response header. When a session expires mid-run it re-authenticates itself, and pauses only when a login genuinely needs a human.

It also keeps the state that makes a second run cheaper: which threats are already confirmed, which probe proved each one, and which lenses have stopped producing.

  • Wall-clock. A verify run is measured in tens of minutes per round, most of it generating and running probes.
  • Agent calls. Annotation runs every round, and enrichment and write-back are agent calls too. Running the tools by hand costs you none of that.
  • A dependency on both products at once. A run is only as current as the cxg and guardlink it drives. It pins their orchestrator files and refuses to resume across drift, which is protection that shows up as friction.
  • More output to read. A cxg pentest report is one artifact. A run leaves a ledger, a summary, per-finding advisories, a triage file, an event log and the probes themselves.

guardlink and cert-x-gen ship on their own schedules, and every reader Bravos has of their artifacts is a hand-written pick of named keys. A key that grew, or a block written in a new shape, is not an error: it is never seen at all, and the nothing that comes back reads as a clean result.

So Bravos declares what it needs of each artifact and takes a census against the one that actually arrived. A field nothing consumes, a value it has no meaning for, a value it refused, or a question the artifact never answered is listed in the run’s health banner in the dashboard, and the counts are marked as a floor rather than a failure. A tool that grew a field has not made the findings wrong, only Bravos’s reading of it incomplete.