init — a starter configuration¶
veridor init reads an OpenAPI specification and writes a configuration file
you then edit. It issues no request. It does not know your policy: every
expectation it writes is a guess, and the file says so in its own comments.
It has two modes, one for each way of using Veridor:
--for |
Writes | For |
|---|---|---|
run (default) |
one check per in-scope operation, owner allowed and other_tenant denied on each |
run: you declare expectations and the gate holds you to them |
drift |
no checks; two identities and one specimen per templated route | drift: nothing is declared, and the gate reports what moved between two maps |
When to use it¶
Use it when you have a specification and no configuration yet. It saves typing the identities, the check skeletons and the request paths. It does not save you the part that matters, which is deciding what each identity may do.
You do not need it. A hand-written configuration works the same way; see the
configuration reference.
run, mutate, capsule and verify need no specification at all. Only
map, init and coverage require one.
Flags¶
| Flag | Default | Does |
|---|---|---|
--openapi PATH |
required | the OpenAPI 3 or Swagger 2 specification to read. JSON only |
--output PATH |
required | the configuration file to create |
--for {run,drift} |
run |
which scaffold to write |
--base-url URL |
from the specification | the value written into target.base_url. Must be an http or https URL with a host |
--force |
off | replace an existing --output file |
Run it¶
Against the demo specification, fixtures/openapi.demo.json:
Wrote 5 checks from 4 paths to veridor.json (--for run: you declare the policy, `run` gates on it)
target.base_url: http://127.0.0.1:8765 (spec)
Confirm that URL names a test environment before running.
This scaffold contains DELETE, POST checks. Running it writes to the target.
Delete those checks, or pass --read-only to refuse them before anything is sent.
Next:
1. read every check: they are the tool's guesses, not your policy
2. decide about the DELETE, POST checks: delete them, or keep them knowing they write
3. export VERIDOR_OWNER_TOKEN=... VERIDOR_OTHER_TENANT_TOKEN=...
4. replace the path placeholders in 2 checks with real resource ids
5. veridor run --config veridor.json --openapi fixtures/openapi.demo.json --coverage coverage.json
add --read-only to that command to refuse the DELETE, POST checks instead of
issuing them: it exits 2 and names the check, before anything is sent.
Every check is a claim you are making about your API, not a measurement. Edit before you trust.
Expect the first run to be red: this file guesses that 'owner' is allowed everywhere,
including on routes no owner is entitled to. Coverage reports this scaffold as unreviewed until you delete "_generated_by".
…
Exit code 0.
What it writes¶
One generated check, as written:
{
"id": "get-invoice",
"name": "GET /api/invoices/{id}",
"_comment": "Replace {id} with a concrete resource owned by 'owner'; Veridor sends this path verbatim. State what this operation promises: 'owner' allowed, 'other_tenant' denied. Delete the check if this operation has no per-identity authorization boundary.",
"request": {"method": "GET", "path": "/api/invoices/{id}"},
"expect": {
"owner": {"status": [200]},
"other_tenant": {"status": [403, 404]}
}
}
The rules behind every generated file:
- Operations. One check per
GET,POST,PUT,PATCHorDELETEoperation. A specification with none of those is refused with exit2. - Check ids. The
operationId, lowercased and hyphenated (getInvoicebecomesget-invoice). Without one, the method and path. A repeated id gets-2,-3and so on. - Statuses.
owneris allowed with a success status for the method:GET[200],POST[200, 201],PUT[200, 204],PATCH[200],DELETE[200, 204].other_tenantis denied with[403, 404]everywhere. Both sets are chosen so that boundary coverage can classify each check the moment it is written. - Identities.
ownerandother_tenant, each sendingAuthorization: Bearer ${VERIDOR_OWNER_TOKEN}orBearer ${VERIDOR_OTHER_TENANT_TOKEN}. No token is written into the file.ownergets a freshly generated marker, which you still have to plant in data the API returns. - Annotations. A top-level
_commentlist, a_commenton each check, a note on any operation the specification marks deprecated, and_generated_by. See annotations. target.base_url. Taken from--base-urlwhen given (printed as(flag)). Otherwise from the specification: the firstserversURL, with server variables replaced by their defaults, or a Swagger 2host,schemesandbasePath(printed as(spec)). When neither gives a usable URL,https://staging.example.comis written (printed as(placeholder)), and a relative server URL such as/apiis applied to the request paths instead.timeout_secondsis10.
The --for drift file has the same two identities and the marker, no
checks, and a matrix section with one specimen per templated path whose
value is REPLACE-WITH-A-REAL-ID:
veridor init --for drift --openapi fixtures/openapi.demo.json --output drift-scaffold.json --base-url http://127.0.0.1:18805
Wrote a drift scaffold for 4 paths to drift-scaffold.json (--for drift: no checks, nothing declared)
target.base_url: http://127.0.0.1:18805 (flag)
1 route(s) need a real object id. Until each one has a specimen it is
not probed at all, and `map` refuses to send REPLACE-WITH-A-REAL-ID:
/api/invoices/{id}
…
The rest of that output is the five-step drift workflow, documented on drift.
Its last line says coverage reports the scaffold as unreviewed. Measured, the
standalone veridor coverage refuses this file instead, with
checks must be a non-empty list and exit 2, because it declares no checks.
Under veridor map, coverage is reported as not measured on this path.
The first run is refused until you edit it¶
The examples below use the demo target, started with
--vulnerable --port 18805, so the scaffold was written again with that
address:
veridor init --openapi fixtures/openapi.demo.json --output veridor.json --force --base-url http://127.0.0.1:18805
Wrote 5 checks from 4 paths to veridor.json (--for run: you declare the policy, `run` gates on it)
target.base_url: http://127.0.0.1:18805 (flag)
…
An unedited --for run scaffold does not run. Exporting the tokens is not
enough: a check path that still carries {id} is refused before any request,
because sending a literal {id} would draw a refusal that reads as a finding.
export VERIDOR_OWNER_TOKEN=owner-token VERIDOR_OTHER_TENANT_TOKEN=outsider-token
veridor run --config veridor.json --read-only
veridor: 2 check(s) still contain an unsubstituted path placeholder, so they name no concrete resource:
get-invoice (/api/invoices/{id})
delete-invoice (/api/invoices/{id})
Veridor sends the path verbatim and never enumerates ids, so each of these would request a literal '{id}' and report the refusal as a finding. Replace each placeholder with a real resource id. If a path genuinely contains braces, percent-encode them as %7B and %7D.
Exit code 2. Without the tokens exported, the refusal comes earlier:
veridor: environment variable 'VERIDOR_OWNER_TOKEN' is not set, also exit 2.
The scaffold was then edited three ways: the DELETE and POST checks were
deleted, and {id} became inv-100, an invoice the demo serves to
owner-token.
veridor run --config veridor.json --read-only --openapi fixtures/openapi.demo.json --coverage coverage.json
Veridor run 20261110T043430Z-9b2cec: 3/6 probes matched their declaration, 1 vulnerable, 2 unexpected denial, 0 inconclusive
Target: http://127.0.0.1:18805
…
- [unexpected_denial] list-invoices / owner: Identity 'owner' received HTTP 404 (declared: one of [200]). …
- [vulnerable] get-invoice / other_tenant: Identity 'other_tenant' received HTTP 200 (declared: one of [403, 404]).
- [unexpected_denial] read-audit-log / owner: Identity 'owner' received HTTP 403 (declared: one of [200]). …
…
THIS NUMBER IS NOT YOURS YET. This configuration still carries
'_generated_by', so it is `veridor init` output: …
Exit code 1. Two of the three findings are about the scaffold's guesses, not
a leak: the demo does not serve GET /api/invoices at all, and its owner
token is not an administrator, so 403 on the audit log is the right answer.
The third, get-invoice / other_tenant, is the demo's real cross-tenant bug.
It is graded ungraded in the run's Evidence: line because the scaffold
declares no contains_marker or excludes_marker yet. That is what the
closing lines of init mean by "expect the first run to be red". Correct the
expectations and the declaration becomes yours. Delete _generated_by when
you have read every check, and coverage stops calling the number unreviewed.
Refusals¶
Each exits 2 and writes nothing:
| Situation | Message |
|---|---|
--output exists |
refusing to overwrite veridor.json; pass --force to replace it |
--output is the specification |
--output must not overwrite the OpenAPI specification (with or without --force) |
| a YAML specification | api.yaml is not JSON. Veridor parses JSON specs only -- …, followed by a conversion command |
--base-url ftp://x |
--base-url must be an http(s) URL with a host |
| no in-scope operation | … declares no in-scope operations, so there is nothing to generate. … |
Exit codes¶
| Code | Meaning |
|---|---|
0 |
the file was written |
2 |
refused, or the specification did not load; nothing was written |
70, 130 and 143 mean the same as for
every command.
What it does not do¶
- It does not infer policy. The
ownerallowed /other_tenantdenied pattern is applied to every operation, including routes no owner is entitled to. - It does not discover routes, ids or tokens. Operations come from the specification only, and each path placeholder needs a real id from you.
- It does not contact the target, and it does not check that
target.base_urlis a test environment. When the URL came from the specification it asks you to confirm that. - It does not skip writes.
POST,PUT,PATCHandDELETEoperations become checks that write when run. Delete them, or run with--read-only. - It does not add an anonymous identity. Its comments say how: an identity with
no headers, expecting
[401]. That is an authentication assertion and never counts as boundary coverage.