Skip to content

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:

veridor init --openapi fixtures/openapi.demo.json --output veridor.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, PATCH or DELETE operation. A specification with none of those is refused with exit 2.
  • Check ids. The operationId, lowercased and hyphenated (getInvoice becomes get-invoice). Without one, the method and path. A repeated id gets -2, -3 and so on.
  • Statuses. owner is allowed with a success status for the method: GET [200], POST [200, 201], PUT [200, 204], PATCH [200], DELETE [200, 204]. other_tenant is denied with [403, 404] everywhere. Both sets are chosen so that boundary coverage can classify each check the moment it is written.
  • Identities. owner and other_tenant, each sending Authorization: Bearer ${VERIDOR_OWNER_TOKEN} or Bearer ${VERIDOR_OTHER_TENANT_TOKEN}. No token is written into the file. owner gets a freshly generated marker, which you still have to plant in data the API returns.
  • Annotations. A top-level _comment list, a _comment on each check, a note on any operation the specification marks deprecated, and _generated_by. See annotations.
  • target.base_url. Taken from --base-url when given (printed as (flag)). Otherwise from the specification: the first servers URL, with server variables replaced by their defaults, or a Swagger 2 host, schemes and basePath (printed as (spec)). When neither gives a usable URL, https://staging.example.com is written (printed as (placeholder)), and a relative server URL such as /api is applied to the request paths instead. timeout_seconds is 10.

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 owner allowed / other_tenant denied 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_url is a test environment. When the URL came from the specification it asks you to confirm that.
  • It does not skip writes. POST, PUT, PATCH and DELETE operations 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.