Security

A verification service for your database changes deserves a plain answer about what we store, what we never see, and what a breach of us would expose of you — especially when the changes we verify are increasingly written by AI.

db accessoptional — catalogs, READ ONLY txnreadscatalogs onlytable dataneverdsn at restsealed-boxsnapshotsephemeralaiadvisory, never the verdictrulesdocumented, with fixtures
01 — Data

What we store — and what we never do

Stored

  • Migration SQL and findings — the text you sent for checking and the full findings report it produced (rule ids, severities, messages, object names, suppressions). This IS the product's run log: every connected check, kept so your team can see what went wrong and how often.
  • Git metadata — repository, branch, pull-request number and commit for checks the Action or CLI sends, so a check links back to where it came from.
  • AI review observations — when you explicitly request a review, the model's advisory commentary is stored alongside the check it reviewed.
  • Rule IDs and object names in findings history — e.g. BV003 fired on orders.
  • Connection strings, sealed — encrypted with a libsodium sealed box before insert. The keypair lives outside the database, so a database breach alone yields ciphertext.
  • Credential hashes — SHA-256 of team tokens and refresh tokens, never the plaintext.
  • Your GitHub id and login — the whole identity we request at sign-in.

Never stored, never read

  • Rows from your tables — introspection runs in a READ ONLY transaction and queries only pg_catalog and information_schema; the onboarding role grants catalog access and nothing else.
  • Full schema dumps — findings reference rule IDs and object names, not your schema.
  • Plaintext credentials — token values render exactly once, at creation, in your browser.
  • GitHub repo access — the OAuth request carries no scopes beyond public identity; we could not read your code if we wanted to.
02 — Lifecycle

The life of a tier-2 check

Most rules refine their verdict against a live snapshot — escalating on large or hot tables, going silent on provably empty ones; only a few need one to fire at all. Live-schema context exists for exactly as long as one check runs. Nothing from your database survives the request. In credential-free mode the first two steps happen on your machine — the CLI introspects locally and only the structure crosses the wire — and the lifecycle below is the stored-connection variant.

  1. Unseal, connect

    Your connection string is opened from its sealed box just-in-time — for this check only, never in bulk — and a short-lived connection is made to the database you stored.

  2. Introspect catalogs, read-only

    Inside a READ ONLY transaction, we read table sizes, columns, foreign keys, and indexes from the system catalogs. No query touches your tables.

  3. Check in memory

    The rule corpus runs over the migration's parse tree with the schema snapshot as context. The snapshot lives in process memory for the duration of the check.

  4. Discard

    The connection closes and the snapshot is gone. What persists is the findings report: rule IDs, severities, object names.

03 — Principles

How we keep the surface small

Never the primary

The CLI and the service open a READ ONLY transaction and only read catalogs. Point them at a replica or shadow database — we do not verify that it is one. The onboarding SQL we hand you grants catalog access and nothing else.

AI is opt-in, and never the verdict

No SQL or schema data reaches a model unless you explicitly request an AI review of a specific migration — and what comes back is clearly-labeled advisory commentary. It cannot pass or fail a check, and Bolvrk never generates SQL. The deterministic engine alone decides; that is the whole point of a verifier.

Every rule is documented

The CLI is open source (MIT) and bundles the free rule set; the corpus beyond the free set is proprietary and runs only in the hosted service. What we can show you is exactly what each rule does: every rule has its own page with the rationale, the SQL it fires on and the safe fix — the same fixtures our CI asserts. The CLI talks to your database directly; nothing touches our servers unless you pass --remote.

Boring on purpose

One service, one Postgres, no microservices, no message brokers, no third-party analytics in the product. A small surface is the cheapest security control there is.

04 — Credential-free mode

The strongest option: never give us a credential at all

The CLI and CI runner already sit next to your database — so they do the introspection, and send us only the result: schema structure. Table and column names, row estimates, index definitions, the foreign-key graph, write-rate counters. Never rows, never values, never a connection string. The check runs on our side against that metadata, lands in your team's history, and the snapshot itself is discarded — malformed snapshots are rejected outright rather than repaired into a weaker check.

BOLVRK_TOKEN=blv_... bolvrk check migration.sql --db=$SHADOW_DB --remote

Don't take our word for it: bolvrk check migration.sql --db=$SHADOW_DB --remote --show-payload prints the exact request body and exits without sending — audit every byte that would leave your machine.

bolvrk explain follows the same rule for query plans: the file's own statements are planned with EXPLAIN inside a read-only transaction that is rolled back — never EXPLAIN ANALYZE, never executed — and only the plan tree leaves the database.

Storing a connection with us (sealed-box encrypted, opened per-check) remains available for teams that prefer zero-setup CI — but it is optional, not the price of tier-2.

The credential rules apply the same discipline to your own secrets. When a migration carries a plaintext password, a connection string or an API key, the finding masks the value before it is written anywhere — the PR comment, the run log and the SARIF file never repeat it. And bolvrk secrets, the same rules over any file, has no remote mode at all: a credential scan never leaves the machine that ran it.

Outbound notifications carry only what the run log stores — rule ids, severities, messages — and generic webhooks are HMAC-signed (X-Bolvrk-Signature); see the docs.

05 — Credentials

Credentials, in detail

Team tokens

Authenticate the CLI and CI. Stored as SHA-256 hashes and compared in constant time; the plaintext is shown once at creation. Revocation is immediate and per-token, so a leaked CI secret is one click from dead.

at restsha-256revocationinstant

Dashboard sessions

GitHub OAuth with a CSRF-bound state. The browser holds a 15-minute access token in memory only — never in storage — and a long-lived refresh token in an httpOnly cookie scripts cannot read. Refresh tokens are stored hashed and revoked server-side on logout.

access tokenmemory, 15 minrefreshhttpOnly cookie

Connection strings

Sealed-box encrypted (libsodium): sealing needs only the public key, opening needs the secret key held outside the database in the secret manager. Decryption happens per-check, never in bulk, and never lands in logs.

ciphercrypto_box_sealsecret keyoutside the db

What GitHub gives us

The OAuth request has an empty scope list: public id and login, nothing else. No repo contents, no org membership, no email scope.

scopesnoneidentityid + login

Reporting a vulnerability

Questions, disclosures, or anything that looks off: security@bolvrk.com. You'll get a human answer within two business days, credit if you want it, and no legal threats for good-faith research. If a report changes how we handle your data, this page changes with it — it is versioned alongside the code.