vouchwell GitHub

Your agent's audit log is a text file.

It sits on a disk a dozen people can write to, and it is held by the one party with a motive to change it. Vouchwell makes every AI agent action policy-gated before it runs and cryptographically provable afterwards — checkable by someone who has every reason to distrust you.

pre-1.0 Apache-2.0 no dependencies 270+ tests not yet independently reviewed

vw verifyone field changed
Audit · lg_7cedb89a73c2a8e6
─────────────────────────────────
  entries      4
  root         3fa4a5447f2ef21ebc375b2dd6…
  checkpoints  1

✗ 3 problem(s) found:

  signature  entry 1  does not verify
  chain      entry 2  chain break: prev is
             da1a768d8a89…, expected
             d56e6d9f2f7a…
  chain      log      no longer extends the
             checkpoint signed at size 4
             — history was rewritten

! Treat this as evidence of tampering,
  not a corrupted file to repair.

One flipped field. Three independent detections.

Check the evidence yourself.

This is a real log from a real agent session — three blocked actions, two checkpoints, two witnesses. Verification runs in your browser: open the network tab and watch nothing leave. Then try to cheat.

Evidence bundle
Try to cheat
What you already trust — expected root, witness keys
Without this, a bundle proves it is consistent with itself. With it, you also learn you were shown the same history as whoever gave you the root.
One per line, kid publicKey, from the witness operators — never from the bundle.
witness signatures on every checkpoint. Signatures from keys you did not list are not counted.

What “verified” means

The bundle has not been altered since it was signed, every entry is in the tree, and nothing has been left out of a bundle that says it is complete.

It does not mean the keys belong to who you think, or that the log records everything the agent did — only what passed through the proxy.

Two verifiers that don't trust each other

This page runs a second implementation that shares no code with the CLI. The tests run both over honest, tampered and randomly mutated bundles and require the same answer every time.

It found two holes in the first: a bundle with its tail cut off verified clean, and witnesses were counted against keys the bundle supplied itself. Both are fixed — try the last two buttons above.

The same check offline

Nothing here needs this page. With the CLI:

vw check bundle.json \
  --witnesses 1 \
  --witness-keys witnesses.json

One line. No code change.

Vouchwell sits between your agent and any MCP server, speaking the same protocol to both. Everything that isn't a tool call passes through untouched — which matters, because MCP gains methods faster than any proxy can track.

Your agent doesn't know it's there. Until it gets refused.

.mcp.json
// before
{ "command": "npx",
  "args": ["-y", "@acme/mcp-crm"] }

// after
{ "command": "vw",
  "args": ["proxy", "--namespace", "crm",
           "--", "npx", "-y", "@acme/mcp-crm"] }

The policy runs first.

A log that only records is a log of things you failed to stop.

agent session7 tool calls
  ran      ops.query        a harmless read
  ran      ops.refund       $45 refund — inside budget
  ran      ops.refund       $30 refund — $75 committed
  BLOCKED  ops.refund       $90 refund — would reach $165
           budget refunds.daily would be exceeded: 75 already
           committed plus 90 proposed, against a cap of 100 per 24h
  BLOCKED  ops.query        destructive SQL
           destructive SQL from an agent is never permitted
  ran      ops.send_email   approved by dana@acme.test
  BLOCKED  ops.query        a live credential in the args
           arguments contain anthropic_key; policy forbids
           sending these to a tool

Four defaults, chosen for the failure mode

A typo is a load error, not a skipped rule. A misspelled operator inside a deny rule must never quietly read as allow.

A pattern that could hang is a load error too. Patterns run against arguments the agent chose, and one like (a+)+$ takes exponential time on the right input. Those are refused when the policy loads, with the rewrite that fixes them.

Escalation with no approver resolves to deny. A system that degrades into “allow everything” under stress fails open exactly when it mattered.

Budgets commit at decision time. Spend recorded on completion means three pipelined refunds all evaluate against an empty ledger, and every one passes a cap they collectively blow through.

vouchwell.policy.json
{
  "version": 1,
  "rules": [
    { "id": "deny.destructive-sql",
      "when": { "params.sql":
        { "matches": "(?i)\\b(drop|truncate)\\b" } },
      "then": "deny" },

    { "id": "escalate.customer-mail",
      "when": { "target": "*.send_email" },
      "then": "escalate" }
  ],
  "budgets": [
    { "id": "refunds.daily",
      "field": "metrics.amount_usd",
      "limit": 1000, "window": "24h",
      "then": "escalate" }
  ],
  "egress": { "denySecrets": true }
}

Or start from a template

Eleven ready-made policies cover what nearly every deployment needs to stop: secrets and personal data in arguments, destructive SQL, payments over a limit, outbound messages, shell commands like rm -rf, production changes, and runaway loops.

They're ordinary policy, so you can read and edit them. Combine any of them, then replay last week's recorded calls against the result to see exactly what it would have blocked, before it blocks anything.

vw policy template
$ vw policy template secrets destructive-sql \
    --out vouchwell.policy.json
✓ Wrote vouchwell.policy.json: secrets, destructive-sql

$ vw policy test vouchwell.policy.json
  replayed        3 calls
  unchanged       2
  changed         1
    allow → deny  1

  tool       recorded  this policy  rule
  ops.query  allow     deny         destructive-sql.schema.sql

Every action leaves a receipt.

Four properties, each because a specific dispute is foreseeable.

signed

Ed25519, deterministic. No per-signature nonce means no way to leak the key by reusing one — which matters when the signer is an unattended runtime producing thousands of signatures an hour.

chained

Each receipt commits to its predecessor. Remove or reorder one entry and everything after it breaks. Sequence gaps are visible without any cryptography at all.

tree-anchored

RFC 6962 Merkle log — the construction Certificate Transparency uses, checked against its published reference vectors. Inclusion proofs tie a receipt to a root; consistency proofs prove the log only ever grew.

sealed

Arguments are salted commitments, not payloads. The log holds sha256(salt ‖ payload) and a preview with secrets masked — so a receipt is publishable as written.

We attacked our own log — then our own verifier.

The first four are attacks on a log. The last two were attacks on the checker, and both worked until we found them.

AttackCaught by
Edit a receipt in placeQuietly reduce an amount that was already approvedsignature does not verify
Delete a receiptDrop the entry that recorded the blockexpected seq 6, found 8
Re-sign the whole chainSteal the key, remove an entry, rebuild every linkcheckpoint covers 11 entries, log holds 8
Truncate the logKeep only the entries you likeentries have been removed
Cut the tail off an exported bundleEvery remaining proof is genuine; the bundle still claims to be complete. This verified clean before we fixed it.holds 5 of 8 entries — entries were left out
Invent your own witnessesAdd fresh keys to the bundle and sign your own checkpoint. Witnesses used to be counted against the bundle's own keyring.not one of the witnesses you pinned

The third row is the one that matters. A hash chain alone cannot catch it — the rewrite is internally consistent. What convicts it is a checkpoint published before the rewrite, which the new history can no longer reproduce.

Two problems nobody else solves.

The split view

A log that signs its own root can keep two histories and show each auditor a different one. Both verify perfectly in isolation. This is the one attack a self-hosted transparency log cannot defend against alone.

Independent witnesses close it. A witness counter-signs a root only if it extends the last root it saw, and refuses to sign two roots at the same size. Pin the witnesses you chose, and a split view needs every one of them to collude.

And it happens without anyone remembering to. List your witnesses once, and every session ends with each of them signing. A witness restored from a backup catches up from a journal it keeps outside its database, rather than forgetting what it signed and vouching for a fork.

auditor
$ vw check evidence.json \
    --witnesses 2 --witness-keys witnesses.json

✓ Verified. Every receipt is signed and
  provably part of this log.

Erasure vs. audit

A deletion request and an immutable audit trail are, on the face of it, two duties in direct conflict. Salted commitments dissolve it.

Destroy the salts, keep the proof. Those payloads become permanently unopenable — by you, by a court, by whoever steals the directory in 2029 — while every signature, chain link and inclusion proof still verifies.

vw shred --before 2026-01-01 --yes
✓ shredded 1 payload commitment
✓ the audit trail still verifies
  end to end

For teams: a hub you don't have to trust.

One log your whole organisation writes to, with shared policy, an approvals inbox and witnesses — without the hub ever being able to forge a receipt, or to stop you working when it's down.

The hub never holds a signing key

Agents sign locally. The hub verifies every receipt on arrival and stores only what it can verify. That leaves it able to do exactly three dishonest things, each with a defence: altering a receipt breaks the signature, dropping one breaks the chain, and showing two customers two histories is what witnesses exist to refuse.

And it isn't load-bearing

An agent whose hub is unreachable keeps running, keeps recording locally, and ships the backlog when it returns. An audit service that could halt production would be removed from production after its first outage.

from source
$ git clone https://github.com/vouchwell/vouchwell
$ cd vouchwell && npm install

$ npm run hub -- bootstrap
$ npm run hub -- serve

$ vw remote add --url http://localhost:8787 \
    --token <agent token>
$ vw proxy --namespace crm -- npx -y @acme/mcp-crm

vouchwell: policy default v1 (7991cca9) from http://localhost:8787
blocked ops.refund — budget refunds.daily would be exceeded
vouchwell · 11 receipts · 2 blocked · hub in sync
shared policy

Versioned and immutable. Agents fetch the active one at startup and record its hash in every receipt, so a decision can always be traced to the exact rules in force.

approvals inbox

Escalations reach a human. Undecided requests expire into a denial — never into an approval. Reviewers see redacted arguments.

tenant isolation

Every row names its organisation. A credential cannot reach another tenant's data by any route, including by guessing an id — and agent keys are pinned to a single log.

keys out of the database

A KMS or HSM can hold them. Signing goes through a three-member interface, so a misconfigured signer disables signing rather than quietly falling back to a local key.

witnessed automatically

Name up to five outside witnesses. Every checkpoint goes to each in the background. A witness refusing because two histories disagree lands in your audit trail, not a log file.

streams to your SIEM

Splunk, Datadog, OpenTelemetry or a signed webhook. Receipts and the hub's own audit trail, with nothing lost while a destination is down. The arguments an agent sent never leave the hub.

Vouchwell's witness keys

Loading the published record…

This page is served by Vouchwell, so it is not what makes these keys trustworthy. The record is append-only and every change is a public commit — its history is what shows a key was never quietly swapped. Pin from a commit you trust, as the record's README explains. And a Vouchwell witness is redundancy, not independence, for a log Vouchwell also hosts.

What's free, and what won't be.

The line is drawn on a principle, not on feature-gating: evidence you cannot verify without a vendor's permission is not evidence. Everything that makes a receipt independently checkable is free forever. What gets sold is infrastructure and independence — hosted witnesses, retention, and the parts a compliance team asks for by name.

Available now

Open

Self-hosted, self-witnessed. Complete and unlimited — this is everything on this page.

  • Receipt format, spec, verifier
  • Policy engine, MCP proxy, CLI
  • Self-hosted log and hub
  • Apache-2.0, forever
Start free
$99/mo

Team

A team that wants an independent witness without running one.

  • 3 hosted witnesses
  • 1M receipts/mo, 1yr retention
  • Slack approvals
Get notified

Not yet live — see below

$999/mo

Business

Compliance exports and SSO, for the team an auditor is now asking about.

  • 5 witnesses across orgs
  • 25M receipts, 7yr retention
  • AI Act / SOC 2 / ISO 42001 exports, SSO
Get notified

Not yet live — see below

From $60k/yr

Enterprise

Your keys never leave your custody, and neither does your data's retention policy.

  • BYOK / HSM, key rotation
  • Private witnesses, custom retention
  • Audit support, SLA
Talk to us

Not yet live — see below

Revenue share

Witness operator

Run a node. An audit firm, insurer or consortium is worth more as a witness than as a customer.

  • You hold the key, not us
  • A distribution channel and a node
  • First movers set the terms
Talk to us

Not yet live — see below

Only Open exists today — it's everything on this page, and it stays free and unlimited regardless of what ships next. The hosted tiers need real infrastructure and, for the witness network to mean anything, real independent operators — not just more code — so they aren't live yet. Get notified opens a public GitHub issue (no account required to read it, one to post); if you'd rather not post publicly, SECURITY.md has the private route.

What it doesn't do.

A security tool that overstates its guarantees is worse than none, because it buys confidence it hasn't earned.

Known limits, in full

The full analysis lives in the repository: the threat model and how to report a vulnerability.

Start in about a minute.

From source
$ git clone https://github.com/vouchwell/vouchwell
$ cd vouchwell && npm install

$ npm run demo              # attacks a real log four ways
$ npm run vw -- init
$ npm run vw -- proxy --namespace crm -- npx -y @acme/mcp-crm

Node 22.13 or newer for the hub; the core and the CLI run on Node 20.11. No dependencies to install — the whole thing is the standard library.

The verifier, the format, the policy engine and the proxy are Apache-2.0 and stay that way. Evidence you can't check without a vendor's permission isn't evidence.