The three verbs¶
Everything Veridor does is one of three questions, asked in this order.
| Verb | The question | What you get |
|---|---|---|
map |
What don't I cover, and what would a compromise reach? | An access matrix, a blast radius per identity, boundary coverage, ranked anomaly candidates, and a diff against an earlier matrix |
run |
Does the boundary hold, and did it change? | Findings, evidence grades, a baseline diff, SARIF, and the CI exit code |
mutate |
Would my suite actually catch a regression? | A mutation score and every mutant your checks failed to kill |
Discover, declare, gate, then verify the gate. map proposes; you decide
what to declare; run holds you to it; mutate tells you whether holding you
to it means anything.
Only run gates on what it discovered¶
Nothing map finds moves its exit code. An inconsistency it noticed is a
hypothesis, not a finding, and discovery must not fail somebody's build. map
still exits 3 when its grid measured nothing, or, with --require-complete,
when part of it went unmeasured; see exit codes. A
candidate produces no SARIF result, never enters .veridor/runs or
latest.json, and never moves an exit code.
There is one opt-in gate, and it is not about a finding at all.
--fail-on-drift fails when this run's matrix differs from
a baseline you recorded earlier. That is a fact about two runs rather than a
judgement about one.
mutate has an exit code of its own, and it is never a run's. A surviving
mutant says your declaration is weak. It says nothing about your target.
| Verb | 0 |
1 |
2 |
3 |
|---|---|---|---|---|
map |
it produced a map | you asked --fail-on-drift and the matrix moved |
refused, or the inputs would not load | nothing in the grid was measured; or --require-complete and part of it was not; or, under a drift gate, no cell was measured in both runs |
run |
nothing the gate fails on was found | the gate failed | configuration, specification, path-safety or undefined-coverage error | the gate passed but something else did not |
mutate |
every mutant was killed | at least one survived | nothing was measured, or a mutant was unresolved | --require-evaluable and a mutant could not be evaluated |
The full breakdown, including 70, 130 and 143, which any command can
end with, is in exit codes.
What each verb needs¶
run, mutate, capsule and verify take a hand-written veridor.json and
nothing else. That is the entire gate: detection, evidence grades, the
regression diff, SARIF, and a portable proof.
map, init and coverage additionally require an OpenAPI 3 or Swagger 2
JSON specification, because they read a route inventory and Veridor never
discovers routes on its own. If you have no specification, a route-list stub
with empty operation objects is enough:
{"openapi": "3.0.0", "info": {"title": "mine", "version": "1"},
"paths": {"/api/invoices": {"get": {}}, "/api/invoices/{id}": {"get": {}},
"/api/admin/audit": {"get": {}}}}
Note
Specifications must be JSON. Veridor has no YAML parser by design: a
hand-written one could silently drop routes, and a dropped route inflates
the coverage number. Convert once with
npx @redocly/cli bundle openapi.yaml -o openapi.json.
What each verb writes to your target¶
| Verb | Methods sent by default | Rate | Plan refused above |
|---|---|---|---|
map |
GET and HEAD only |
2 req/s, sequential, capped at 10 | --max-requests, default 500, absolute ceiling 20,000 |
run |
whatever your configuration declares | unthrottled | — |
mutate |
whatever your configuration declares, re-issued | --min-interval, default 0.5s |
--max-requests, default 5,000 |
run is unthrottled because it issues the requests you wrote, once each, plus
its own marker probes and liveness controls. map
fans a specification out across every identity, which is a scanning-shaped
workload and is treated as one — see safety posture.
--read-only refuses, before any request, any configuration that can issue a
method outside GET, HEAD and OPTIONS; it is honoured by run and by
mutate at both tiers.
The utilities¶
These are commands you reach for while using the three verbs, not headline
commands. veridor --help shows the same split.
| Command | Does |
|---|---|
init |
writes a starter configuration from a specification. Every check it writes is a guess, not a measurement |
coverage |
measures boundary coverage alone, issuing no request |
new-marker |
prints a marker that passes validation |
report |
renders an existing run JSON as SARIF, JUnit, GitLab SAST, Code Quality or Markdown, or a drift JSON as Markdown, without repeating the probes |
capsule |
writes a portable, runnable proof of one finding, carrying no credential its rule recognises |
serve |
opens a local viewer over .veridor/, on 127.0.0.1:8787 |
verify is an alias for run, and additionally takes a capsule file, which it
treats as untrusted input (safety posture).
Where the verbs belong¶
run is the verb that belongs in CI. mutate is something you do deliberately
at a desk, because its output is written for a human. map is too — except for
drift, which is built for CI and is the one way to get a gate
without declaring anything.
One CI step is one verb. If you want both a declared gate and drift, use two steps: they exit non-zero for different reasons, and a shared exit code would hide which one failed. See the GitHub Action.
What none of them establish¶
Read claims and limits before you read a report. In short:
- Boundary coverage says a cross-identity denial is asserted. It never says the boundary is enforced.
- A candidate is an inconsistency between two observations — a hypothesis to declare and test. It is never a finding.
- Blast radius counts operations an identity was observed to reach, as a lower bound. It never counts objects.
- A mutation score says perturbing your declaration changes the verdict. It says nothing about operations your suite does not cover.
- A finding says observed behaviour differed from your declaration. It does not say the declaration was right.
Your configuration is the policy. A wrong declaration and a real vulnerability produce the same finding, and Veridor cannot tell them apart.