Vouchwell
Read, verify and export a tamper-evident log of what an AI agent did. Read-only.
ماذا يمكنك أن تفعل مع Vouchwell؟
- It cannot prove an action it never saw. Vouchwell records what passes
- The cryptography has not been reviewed by anyone independent. It is
- A restored hub cannot detect its own staleness. The evidence was in the
- **It cannot stop an attacker with the signing key from writing false
- Witnesses are only as independent as you make them. Three witnesses on
- Redaction is best-effort. The detectors catch known credential formats
التثبيت
أضف هذا إلى إعدادات MCP في عميلك (Claude Desktop: claude_desktop_config.json، وCursor: .cursor/mcp.json، ويستخدم VS Code وغيره الصيغة نفسها «mcpServers»).
vouchwellclaude mcp add vouchwell -- npx -y vouchwell@0.8.0 mcp --log
{
"mcpServers": {
"vouchwell": {
"command": "npx",
"args": [
"-y",
"vouchwell@0.8.0",
"mcp",
"--log"
]
}
}
}
التوثيق
مأخوذ من ملف README للمشروع على GitHub ويُحدَّث أسبوعيًا. تفتح الروابط على GitHub.
المحتويات
Vouchwell
Tamper-evident receipts for AI agent actions.
Formerly Proofwire. Renamed after 0.5.0 because another company was already using the name. The
proofwireand@proof_wire/*packages point here; existing logs, settings, hubs and evidence keep working unchanged.
Your agents spend money, send mail, and touch customer data. When something goes wrong, "our logs say it didn't" is not an answer — your logs are a text file you can edit.
Vouchwell makes every agent action policy-gated before it runs and cryptographically provable afterwards. One line of config. No code change.
Run it standalone on one machine, or as a hub your whole organisation writes to — with shared policy, an approvals inbox, tenant isolation, and independent witnesses that make a hosted log worth believing.
Check a real log in your browser → Then try to cheat it: edit a receipt, cut the tail off, invent your own witnesses. Nothing you paste leaves the page.
npm install -g vouchwell
vw init
Then wrap any MCP server:
// .mcp.json — before
{ "command": "npx", "args": ["-y", "@acme/mcp-crm"] }
// after
{ "command": "vw", "args": ["proxy", "--namespace", "crm", "--", "npx", "-y", "@acme/mcp-crm"] }
That is the whole integration. Every tool call now hits your policy first, and every decision — allowed, blocked, or escalated to a human — lands in an append-only log that an outside party can verify without trusting you.
See it in one command
git clone https://github.com/vouchwell/vouchwell && cd vouchwell
npm install
npm run demo
The demo runs a real agent session through a real proxy, then attacks the resulting log four ways and shows each attack being caught:
1. An agent works through a queue of tasks
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…
BLOCKED ops.query destructive SQL
ran ops.send_email outbound mail (approved by dana@acme.test)
BLOCKED ops.query a live credential in the args
3. Four ways to cover your tracks, and what each one costs
Edit a receipt in place ✓ caught — signature does not verify
Delete a receipt ✓ caught — expected seq 6, found 8
Re-sign the whole chain ✓ caught — checkpoint covers 11 entries, log holds 8
Truncate the log ✓ caught — entries have been removed
For teams: the hub
docker compose up -d
docker compose exec hub node packages/server/src/bin.js bootstrap
That runs on your machine over plain HTTP, for trying it out. For a real
server with a domain, automatic HTTPS and a hardened setup, use deploy/:
docs/DEPLOY.md walks through it end to end.
Built and driven for real: CI's
dockerjob builds this image, boots it, registers a log, signs and pushes receipts, checkpoints, fetches the bundle back and verifies it, confirms a bad token is refused, and checks the container actually runs read-only and non-root (scripts/docker-smoke.mjs). It also caught a real bug: a receipt missing an actor field crashed the hub with a raw SQLite error instead of a clean 4xx — fixed inbuildReceipt/verifyReceipt. The two- container witnessing flow below (docker compose up -d witness,vw remote add --name witness,vw cosign --remote witness) was run by hand against both containers, through tovw check --witnesses 1on the resulting bundle.
vw remote add --url https://hub.acme.com --token <agent token>
vw proxy --namespace crm -- npx -y @acme/mcp-crm
The proxy now fetches your organisation's active policy at startup, enforces it, records locally, and streams receipts to the hub — where an operator sees this:
Acme Financial ✓ verified
11 receipts · 30d 3 actions blocked 1 log 11 entries
RECENTLY BLOCKED
48s ops.query DENY arguments contain anthropic_key; policy forbids sending these to a tool
48s ops.refund DENY budget refunds.daily would be exceeded: 45 already committed plus 90
proposed, against a cap of 100 per 24h
1m ops.query DENY destructive SQL from an agent is never permitted
What the hub adds:
| Shared policy | Versioned, immutable, rolled back by version. Agents fetch the active one and record its hash in every receipt. |
| Approvals inbox | Escalations reach a human in the console. Undecided requests expire into a denial — never into an approval. |
| Tenant isolation | Every row names its org. A credential cannot reach another organisation's data by any route, including by guessing an id. |
| Witness service | Counter-signs only roots signed by the key it bound the log to, and refuses two roots at one size. This is what defeats a split view. |
| Its own audit trail | Every administrative action is hash-chained. We ask you to trust a tamper-evident record, so ours is one too. |
The hub is not trusted and does not need to be. It never holds a signing key, so it cannot forge a receipt — and it is not load-bearing either: an agent whose hub is unreachable keeps running, keeps recording locally, and ships the backlog when it returns.
Keys can live in a KMS or HSM rather than the hub's database
(VOUCHWELL_SIGNER), backups and restores have a drilled runbook, and
invitations and password resets are built in.
Full deployment and operations guide: docs/HUB.md · for
reviewers: docs/AUDIT-BRIEF.md.
Why this is different
Everyone is building agent observability — dashboards that show you what your agent did, which you have to take on faith. Vouchwell builds agent evidence: a record whose integrity a third party can check independently, using nothing but the file you hand them.
| Observability tools | Vouchwell | |
|---|---|---|
| Shows what the agent did | ✅ | ✅ |
| Blocks the action before it runs | ❌ | ✅ |
| Survives an insider with DB access | ❌ | ✅ |
| Verifiable by someone who distrusts you | ❌ | ✅ |
| Erasure without breaking the audit trail | ❌ | ✅ |
| Survives the vendor being the adversary | ❌ | ✅ |
The distinction matters the day a regulator, an insurer, or opposing counsel asks "prove it." A dashboard is a claim. A signed, witnessed Merkle root is evidence.
How it works
1. Policy runs first
{
"version": 1,
"rules": [
{ "id": "deny.destructive-sql",
"when": { "params.sql": { "matches": "(?i)\\b(drop|truncate|delete\\s+from)\\b" } },
"then": "deny",
"reason": "destructive SQL from an agent is never permitted" },
{ "id": "escalate.customer-mail",
"when": { "target": "*.send_email" },
"then": "escalate" }
],
"budgets": [
{ "id": "refunds.daily", "match": { "target": "*.refund" },
"field": "metrics.amount_usd", "limit": 1000, "window": "24h", "then": "escalate" }
],
"egress": { "denySecrets": true }
}
Four defaults chosen so the failure modes are safe:
- A typo is a load error, not a skipped rule. A misspelled operator in a
denyrule must never quietly read as "allow". - A pattern that could hang is a load error too.
matchesruns against arguments the agent chose, and a pattern like(a+)+$takes exponential time on the right input. Patterns that repeat a group containing its own repetition, alternatives or optional parts, and backreferences, are refused when a policy loads or is published, with a suggested rewrite ((a|b)+is[ab]+). escalatewith no approver resolves todeny. A system that degrades into "allow everything" under stress is worse than no system.- Budgets commit at decision time, not on completion. Otherwise three pipelined refunds all evaluate against an empty ledger and every one passes a cap they collectively blow through.
Start from a template
Eleven ready-made policies cover what nearly every deployment needs to stop:
| Template | What it does | |
|---|---|---|
secrets | Refuses any call carrying an API key, token, private key, card number or IBAN | |
no-personal-data | Also refuses emails, phone numbers and SSNs: for tools that must never see personal data | |
destructive-sql | Refuses DROP, TRUNCATE, ALTER TABLE, GRANT/REVOKE, and DELETE or UPDATE with no WHERE | |
sql-writes-need-approval | A person approves every INSERT, UPDATE, DELETE and MERGE | |
payments | A person approves any payment over $500, or past $2,000 a day per person | |
outbound-messages | A person approves every send, post, reply, forward and publish | |
outbound-rate-limit | Messages go out unapproved, at most 30 a session per hour | |
shell-safety | Refuses rm -rf, mkfs, force-pushes, chmod 777 and `curl … \ | sh`; file deletes need a person |
production-guard | A person approves anything whose environment is production | |
loop-guard | Stops the same tool after 60 calls in 5 minutes | |
read-only | An allowlist: lookups run, everything else is refused |
vw init --template secrets,destructive-sql,payments # a new project
vw policy template shell-safety loop-guard --out vouchwell.policy.json
vw policy template payments --explain # what it assumes
Combined, refusals are placed ahead of allows, so read-only can never let
through something destructive-sql refuses. The output is ordinary policy:
read it, edit it, push it to a hub with vw policy push, or load it in code
with new Policy(composePolicy(['secrets', 'payments'])). Templates match the
names most tools use (send_email, params.sql, params.command), which may
not be yours, so check them against your own traffic first, as below.
Try a policy before it can block anything
vw proxy --monitor -- npx -y @acme/mcp-crm
Monitor mode evaluates the policy exactly as enforcement would, then forwards
every call anyway, including ones the egress guard would stop. Nothing is
blocked and no one is asked to approve anything. It is the status quo plus a
record. vw log --would-block and vw stats then show what the policy would
have stopped, and which rule did it, against your real traffic. When that list
contains only things you want stopped, drop the flag.
The receipts stay truthful. A call that ran is recorded as allow, never as a
deny that didn't happen. It also carries "enforced": false and, where the
policy objected, "wouldBe": "deny" or "escalate", all inside the signature.
So a bundle shows an auditor that the policy was only observed, and a monitored
call counts against budgets because it really spent the money. Set
"monitor": true in vouchwell.config.json to make it the default on a
machine; --enforce overrides that. A hub's policy cannot switch it on.
Before you drop --monitor, or before you change a policy that is already
enforcing, replay what you recorded against the new version:
vw policy test vouchwell.policy.json
vw policy test next.policy.json --fail-on-change # exit 1 if any verdict differs
It lists each call whose verdict would change (deny → allow, allow →
escalate and so on) and the rule that caused it. Each call is judged at the
time it was recorded, and budgets count only what the new policy would have let
through. The log keeps redacted arguments, not raw ones, so a call with masked
values is marked ≈: a rule that looked at the masked value cannot be replayed
exactly.
2. Every action gets a receipt
{
"v": 1, "log": "lg_14b9c3dd", "seq": 7, "prev": "3fa4a544…",
"ts": "2026-09-20T17:19:00.057Z", "phase": "intent",
"actor": { "agent": "claude-opus-5", "principal": "ops@acme.test", … },
"action": {
"kind": "tool_call", "target": "ops.refund",
"metrics": { "amount_usd": 45 },
"params": { "hash": "74155b6d…", "size": 42, "preview": { "order": "ord_8821", … } }
},
"decision": { "outcome": "allow", "policy": "8ab2e528…", "rules": ["refunds.under-cap"] },
"attest": { "alg": "ed25519", "kid": "pw106d7bf7…", "sig": "…" }
}
Four properties, each because a specific dispute is foreseeable:
- Signed — Ed25519, deterministic, key never leaves the runtime.
- Chained — each receipt commits to its predecessor's hash. Delete or reorder one entry and everything after it breaks.
- Tree-anchored — RFC 6962 Merkle log, the same construction Certificate Transparency uses. Inclusion proofs tie one receipt to a published root; consistency proofs prove the log only ever grew.
- Sealed — arguments are stored as salted commitments plus a redacted preview, never as raw payloads.
3. Nothing sensitive is in the log
The log holds sha256(salt ‖ payload) and a preview with secrets masked. Salts
live in a separate file. So:
- A receipt is publishable as written. No "sanitise before exporting" step to forget.
- Erasure and audit stop being in conflict.
vw shred --before 2026-01-01destroys the salts. 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. That is a real GDPR Article 17 erasure that does not gut your audit trail.
4. Witnesses close the last hole
A log signing its own root can show two different histories to two auditors — the classic split-view attack, and the one thing a self-hosted log cannot defend against alone. Independent witnesses only counter-sign a root that extends the last one they saw, so a split view requires every witness to collude.
vw witness keygen # on the witness's machine
vw trust pw1a4f… <publicKey> # on the log's machine
vw check evidence.json --witnesses 2 --witness-keys witnesses.json
witnesses.json holds the witnesses' public keys as their operators
published them — never taken from the bundle. A bundle's own keyring comes
from the party whose honesty is in question, so it cannot vouch for witnesses:
an operator could add any number of fresh keys and "witness" their own
checkpoints. Only signatures from keys you pinned are counted, and asking for
--witnesses without pinning any is refused rather than answered by counting
whatever the bundle contains.
Witnessing doesn't have to be a chore. Put "witnesses": ["w1", "w2"] in
vouchwell.config.json and every vw proxy session ends with each witness
signing. On a hub, vw witnesses add has every hub checkpoint co-signed, and
a witness refusing a checkpoint lands in the audit trail. See
the hub guide.
5. Your SOC sees it where it already looks
A hub streams receipts and its own audit trail to Splunk, Datadog, OpenTelemetry or a signed webhook. Delivery is at least once, with nothing lost while a destination is down, and an action's parameters never leave the hub:
VOUCHWELL_STREAM_TOKEN=<HEC token> vw streams add splunk --type splunk --url https://splunk.example.com:8088
vw streams add soc --type webhook --url https://soc.example.com/hook --receipts blocked
Commands
Setup
vw init create a log, a starter policy, and a config
Run
vw proxy -- <cmd...> wrap an MCP server; enforce policy, write receipts
--namespace <ns> prefix tool names in receipts
--principal <id> who the agent is acting for
--approve tty|webhook|deny how escalations get resolved
--monitor block nothing; record what policy would block
--enforce gate even if the config says "monitor": true
Inspect
vw log recent receipts [--tail N --denied --would-block --unfinished --target X --json]
vw stats totals, spend, busiest tools
vw policy test [file] replay the log against a policy [--fail-on-change --json]
vw dash browsable dashboard [--port 7788]
Prove
vw verify audit the local log end to end [--fail-on-unfinished]
vw prove <seq> inclusion proof for one receipt
vw export [file] evidence bundle for a third party
vw check <file> verify a bundle with nothing but itself [--log-key kid=pub --json]
vw report evidence pack for auditors (AI Act, SOC 2); see docs/EVIDENCE.md
Hub
vw remote add --url <hub> --token <key> connect this machine (https, or
http to localhost; --insecure to override)
vw push ship local receipts the hub is missing
vw remote-verify <log> verify a hosted log from outside
vw policy push|pull|list manage the org's shared policy
vw cosign have witnesses counter-sign your latest root
(--remote a,b, or "witnesses" in the config)
vw witnesses list|add|remove outside witnesses for every hub checkpoint (admin key)
vw streams list|add|test events to Splunk, Datadog, OTel, a webhook (admin key)
Govern
vw keys public keys to publish for verifiers
vw witness keygen create an independent witness identity
vw trust <kid> <pubkey> trust a witness or another signer
vw shred --before <date> destroy payload commitments, keep the audit trail
vw verify exits non-zero when a log has been altered — put it in CI. It also
lists calls that were authorised and sent but never finished (the agent died
mid-call); --fail-on-unfinished makes those exit 3.
Library use
Not on MCP? Wrap the tools themselves. The core is a small, dependency-free ES module.
import fs from 'node:fs';
import { ProofLog, Policy, Recorder, PolicyDenied } from '@vouchwell/core';
const rec = new Recorder({
log: ProofLog.open('.vouchwell'),
agent: 'support-bot',
principal: 'ops@acme.com',
policy: Policy.parse(fs.readFileSync('vouchwell.policy.json', 'utf8')),
metrics: (tool, args) => (tool === 'stripe.refund' ? { amount_usd: args.amount } : {}),
approver: async (req) => askSomeone(req), // optional: who answers an escalation
});
const refund = rec.wrap('stripe.refund', async ({ order, amount }) => stripe.refunds.create({ ... }));
await refund({ order: 'o_1', amount: 45 }); // checked, recorded, then run; throws PolicyDenied if refused
The same rules as vw proxy, receipt for receipt: the policy (budgets and
rate limits included) decides first; an allowed call gets an intent receipt
before it runs and a linked outcome after; a refused one never runs;
monitor: true records what would have been blocked without blocking it.
Call rec.finalize() on shutdown.
Tools described as objects with an execute function (the Vercel AI SDK's
tool(), Mastra) can be wrapped in one go:
import { recordTools } from '@vouchwell/core';
const result = await generateText({ model, tools: recordTools(rec, { weather, refund }), prompt });
Python agents have the same in vouchwell, with
adapters for LangChain and the OpenAI Agents SDK.
Coding agents
Claude Code does most of its work with its own tools (the shell, file reads and edits, fetches), not MCP servers. Vouchwell records and gates those too, through Claude Code's hooks. The quickest way is the plugin, typed in Claude Code:
/plugin marketplace add Vouchwell/vouchwell
/plugin install vouchwell@vouchwell
Or set it up per project with the CLI, for Claude Code, OpenAI's Codex CLI or Cursor:
vw policy template coding-agent shell-safety secrets --out vouchwell.policy.json
vw hook install # Claude Code
vw hook install --client codex # Codex CLI
vw hook install --client cursor # Cursor
From the next session, every tool call is checked against the policy before it runs, and gets a receipt:
- a refused call is denied, with the policy's reason;
- an escalated one goes to the person at the keyboard;
- the rest are recorded.
Check it in CI
Commit the log as a bundle and let every push check it. The verify action runs the verifier from its own source: no install, no token. It fails the build if a bundle was edited, truncated, or signed by a key other than yours:
- uses: Vouchwell/vouchwell/verify@v0.8.0
with:
log-keys: evidence/log-keys.json # from `vw keys`
Let an agent read its own record
vw mcp serves a log as an MCP server. Any MCP client (Claude Code, Claude
Desktop, Cursor, Codex, and others) can then answer "what did you do?" and
"prove it" from the record itself. Its tools are log_entries, verify_log,
log_stats, check_bundle and export_evidence. Every tool reads; none can
change the log.
{ "mcpServers": { "vouchwell": { "command": "npx", "args": ["-y", "vouchwell", "mcp", "--log", "/path/to/.vouchwell"] } } }
How this repository was built
An AI agent builds Vouchwell, and this repository records it doing so. Its
Claude Code hooks run vw hook on every tool call made while working here.
The logs are published in evidence/ as bundles that
hold commitments only, so nothing from the machine leaks. CI verifies each one
on every push with the verify action, against the keys pinned in
evidence/log-keys.json.
What it does not do
Stated plainly, because a security tool that overstates its guarantees is worse than none:
- It cannot prove an action it never saw. Vouchwell records what passes through it. An agent with a second, unwrapped path to the same API leaves no receipt. Route tools through the proxy and treat unwrapped credentials as the hole they are.
- 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, but that is not the same thing. See
docs/AUDIT-BRIEF.md. - 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. Re-push from every agent after a restore.
- It cannot stop an attacker with the signing key from writing false receipts going forward. It can stop them rewriting the past, once a checkpoint has been witnessed. Keep the key in a KMS or HSM in production.
- Witnesses are only as independent as you make them. Three witnesses on infrastructure you control are one witness.
- Redaction is best-effort. The detectors catch known credential formats and common PII. They will not catch a secret shaped like prose. Commitments, not redaction, are what keep payloads out of the log.
See docs/THREAT-MODEL.md for the full analysis,
including the known gaps.
Status
Cryptography, policy engine, proxy, CLI, the multi-tenant hub and the Python SDK are
complete and covered by 400+ tests: the published RFC 6962 Certificate
Transparency reference tree and RFC 8032 Ed25519 vectors, exhaustive Merkle
proof verification for every tree size up to 128 and every (m, n) consistency
pair up to 48, and end-to-end tests that run a real agent through a real proxy
against a real hub over HTTP.
npm test
Measured on a 4-core laptop: 2,300 receipts/s ingested, 9,500 inclusion
proofs/s, 109 ms to export 4,000 receipts as a verifiable bundle, 2.5 KB per
receipt on disk, and zero external dependencies. Reproduce with
node packages/server/bench/load.js.
Wire format and policy schema are versioned ("v": 1) and will be migrated,
not broken.
Guides: trying it on real agents · Python SDK · deploying a hub or witness · Slack approvals · single sign-on · streaming to your SIEM · evidence packs for auditors · the hub · threat model
License
Apache-2.0. The format, the verifier, and the CLI are open and will stay open: evidence you cannot verify without a vendor's permission is not evidence.
الأسئلة الشائعة
ما هو Vouchwell؟
Read, verify and export a tamper-evident log of what an AI agent did. Read-only.
كيف أثبّت Vouchwell؟
يعمل على جهازك. في Claude Code: claude mcp add vouchwell -- npx -y vouchwell@0.8.0 mcp --log. وفي العملاء الآخرين، الصق إعداد JSON من هذه الصفحة في إعدادات MCP الخاصة بهم.
هل يحتاج Vouchwell إلى مفتاح API؟
لا يذكر إدراجه في السجل أي مفتاح أو سر إلزامي. قد تتطلب بعض الميزات حسابًا لدى الخدمة التي يتصل بها.
هل Vouchwell مفتوح المصدر؟
شيفرته علنية على GitHub، لكن المستودع لا يعلن ترخيصًا قياسيًا.
من يصون Vouchwell؟
ينشره Vouchwell. يدرجه AI.tn لكنه لا يصونه ولا يوصي به.
من سجل MCP الرسمي. الإدراج ليس توصية: راجع شيفرة الخادم وصلاحياته قبل منحه الوصول إلى بياناتك.