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
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.
vw check bundle.json.
What you already trust — expected root, witness keys
| # | Decision | Tool | Phase | Amount | Detail |
|---|
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.
// 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.
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.
{
"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 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.
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.
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.
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.
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.
| Attack | Caught by |
|---|---|
| Edit a receipt in placeQuietly reduce an amount that was already approved | signature does not verify |
| Delete a receiptDrop the entry that recorded the block | expected seq 6, found 8 |
| Re-sign the whole chainSteal the key, remove an entry, rebuild every link | checkpoint covers 11 entries, log holds 8 |
| Truncate the logKeep only the entries you like | entries 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.
$ 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.
✓ 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.
$ 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
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.
Escalations reach a human. Undecided requests expire into a denial — never into an approval. Reviewers see redacted arguments.
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.
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.
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.
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.
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
Team
A team that wants an independent witness without running one.
- 3 hosted witnesses
- 1M receipts/mo, 1yr retention
- Slack approvals
Not yet live — see below
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
Not yet live — see below
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
Not yet live — see below
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
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.
- The cryptography has not been reviewed by anyone independent. It is checked against published RFC 6962 and RFC 8032 vectors rather than only against itself, and against a second implementation that shares none of its code — which is not the same thing. A review brief is written and waiting.
- It cannot prove an action it never saw. An agent with a second, unwrapped path to the same API leaves no receipt. This is the most likely real-world failure, and it is a deployment problem, not a cryptographic one.
- It cannot stop someone with the signing key writing false receipts going forward. It can stop them rewriting the past, once a checkpoint has been witnessed. Keep the key in a KMS.
- Witnesses are only as independent as you make them. Three witnesses on infrastructure you control are one witness.
- A restored hub cannot detect its own staleness. The evidence was in the data the restore discarded; only an agent or an auditor holding later evidence can see the gap.
- There is no hosted service. You run everything yourself — the Dockerfile is built, driven end to end and smoke-tested in CI on every push, but nobody operates it for you yet.
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.
$ npm install -g vouchwell $ vw init $ vw proxy --namespace crm -- npx -y @acme/mcp-crm
$ 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.