Verfy sits in the path of every tool call
It runs on the developer's machine, decides locally and quickly, and never waits on the network. The web platform collects evidence; it is never in the critical path.
The tool-call path
Claude Code hook
verfy hook pre-tool-use receives each call as JSON. Verfy answers deny or ask with the rule and reason, or stays silent so Claude Code's own permissions decide. It never answers allow on its own.
MCP gateway
verfy proxy wraps a stdio MCP server: it scans tools/list, hides poisoned or drifted tools, checks every tools/call and can ask the user through MCP elicitation.
Failure modes
Bad input, a broken policy, an unreadable lock or a full disk all become ask (or deny with fail_mode). Verfy never fails open.
Receipts and the hash chain
The signature covers the canonical JSON of every field (sorted keys, no whitespace, JavaScript number formatting), so the Python CLI and the TypeScript server produce byte-identical input. The repository ships shared test vectors that both implementations must verify.
Team sync
verfy team enroll sends the device's public key; the server returns a device token stored only as a hash.verfy sync uploads receipts in batches of up to 500. The server checks schema, signature, sequence and links, and stores them idempotently.verfy team pull fetches the team policy and team-approved locks.Lock drift
verfy lock diff, then approves locally or proposes the change to the team.Inside a receipt
One record from the published test vectors. Point at a part to see what it proves.
{ "v": "verfy.receipt/1", "seq": 1, "prev": "sha256:5912be441c…", "issued_at": "2026-10-11T09:00:01.000Z", "action": { "tool": "Bash", "args_sha256": "sha256:73f3292934…" }, "decision": { "verdict": "deny", "rules": ["VFY-SH-001"], "policy_sha256": "sha256:66ed6885f8…" }, "kid": "atl_65b60673d6ed884b", "sig": "ktPSqLCI62CgjCYVZ1mNz…"}