Docs

Quickstart

Guard a coding agent on your machine first. Connecting to Verfy Team is optional and can come later.

1. Install

Python 3.10 or newer. No other dependencies (install the optional cryptography extra for faster signing).

$ pip install verfy
$ pip install "verfy[fast]"      # optional: faster signing
$ verfy init
$ verfy check "git push --force"   # exit 1: needs approval

2. Guard Claude Code

The hook checks every tool call before it runs and writes a signed receipt. Review the change first (without --write it only prints it).

$ verfy hook install            # print the settings change
$ verfy hook install --write    # save it (backup kept)
$ verfy ledger show
$ verfy verify

3. Scan and lock MCP servers

$ verfy scan --discover                       # static: how servers are launched
$ verfy wrap ~/.cursor/mcp.json --write       # put servers behind the gateway
$ verfy lock status                           # after first use, tools are locked
$ verfy lock diff filesystem                  # if a definition drifted
$ verfy lock approve filesystem               # review, then accept

4. Fail the pull request when a tool changes

Lock the servers your repository configures and commit the lock. In CI, verfy lock check starts each stdio server from .mcp.json, reads its tools and fails if any definition differs from verfy.lock. The diff appears in the log and as an annotation on the pull request.

$ verfy lock check --update     # shows each definition and its scan findings, then writes verfy.lock
$ git add verfy.lock .mcp.json && git commit -m "Lock MCP tools"
name: Verfy Lock
on: [pull_request, push]
permissions:
  contents: read
jobs:
  lock:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: "3.13"
      - uses: anvarbekovjavohir277-dot/verfy@main   # not public yet; see the note below
        with:
          config: .mcp.json
          lock: verfy.lock

The check runs the server commands in your config, as your MCP client would, so pin package versions and pass any keys they need as CI secrets. Remote (HTTP) servers are skipped. The action lives in the Verfy repository, which is not public yet; until it is, copy action.yml and verfy-cli/ into your own repository and use uses: ./.

Export receipts as SCITT signed statements

Each receipt can be written as a COSE_Sign1 signed statement with CWT claims, the envelope the IETF SCITT architecture (an Internet-Draft) registers with a transparency service. Anyone with your public key can check one offline. Verfy does not run a transparency service.

$ verfy scitt export --out statements/ --issuer https://acme.example/devices/laptop-1
$ verfy key --json > signer.json
$ verfy scitt verify statements/*.cose --pub signer.json

5. Connect to Verfy Team

In the dashboard, open Devices → Add device to get a one-time code (valid for one hour). Only the device's public key is sent.

$ verfy team enroll --url https://verfy.example.com --code atle_…
$ verfy sync            # run it from cron or CI; never inside the hook
$ verfy team pull       # team policy and approved locks

Policy

JSON merged over the defaults. Order of precedence: ./verfy.policy.json, then the team policy from verfy team pull, then ~/.verfy/policy.json.

{
  "on_ask": "deny",
  "fail_mode": "ask",
  "tools": {
    "deny": [
      "github/delete_*"
    ],
    "ask": [
      "merge_pull_request"
    ]
  },
  "network": {
    "allow": [
      "github.com",
      "*.python.org"
    ],
    "unknown": "ask"
  },
  "rules": [
    {
      "id": "no-prod-db",
      "pattern": "prod-db",
      "verdict": "deny",
      "message": "Production is off-limits."
    }
  ],
  "lock": {
    "on_new": "ask"
  },
  "receipts": {
    "include_args": false
  }
}

CLI reference

Verfy CLI commands
CommandWhat it doesExit codes
verfy initCreate a signing key and a starter policy in ~/.verfy0
verfy scan --discoverStatic check of the MCP servers in your Claude Code, Cursor and Claude Desktop configs0 ok · 1 medium · 2 high or worse
verfy scan --discover --liveAlso start each stdio server and scan its tool definitions0 · 1 with --fail-under
verfy scan FILEScan a saved tools/list answer0 · 1 with --fail-under
verfy check "CMD"Pre-check one command or --tool NAME --args JSON without running it0 allow · 1 ask · 2 deny
verfy hook install --writeAdd the PreToolUse hook to ~/.claude/settings.json (--project for this repo)0
verfy hook pre-tool-useThe hook entry point Claude Code calls (JSON on stdin and stdout)always 0
verfy proxy --name N -- CMDRun an MCP server behind the Verfy gatewayserver's exit code
verfy wrap CONFIG --writePut every server in an MCP config behind the gateway (keeps a backup)0
verfy lock checkStart the servers in .mcp.json and compare their tools with ./verfy.lock (for CI); --update writes reviewed changes0 ok · 1 new or removed · 2 drift · 3 a server did not answer
verfy lock status | diff | approve | add | verifyInspect and approve tool definition changes in verfy.lockstatus: 0 ok · 1 new · 2 drift
verfy verifyVerify every signature and hash link in the local ledger0 ok · 1 failed
verfy ledger showList recent receipts0
verfy scitt export --out DIRWrite each receipt as a SCITT signed statement (COSE_Sign1)0
verfy scitt verify FILE…Check statements: COSE signature, subject and the receipt inside0 ok · 1 failed
verfy team enroll --url URL --code CODERegister this device's public key with your team0 · 4 on a team error
verfy syncUpload new receipts to Verfy Team0 · 4 on a team error
verfy team pullFetch the team policy and team-approved locks0
verfy team propose SERVER TOOLAsk admins to approve a changed tool definition0

API

JSON over HTTPS. Errors use RFC 9457 application/problem+json. Full request and response schemas are in docs/API.md.

Verfy API endpoints
MethodPathWhoPurpose
POST/api/v1/devices/enrollenrollment codeRegister a device public key; returns a device token once
POST/api/v1/receipts/batchdevice tokenUpload up to 500 signed receipts (idempotent)
GET/api/v1/policies/currentdevice or memberCurrent team policy
PUT/api/v1/policies/currentowner, adminPublish a policy (simulator runs first)
POST/api/v1/policies/simulatememberCompare a proposed policy with the current one
GET/api/v1/locksdevice or memberTeam locks (?status=approved)
POST/api/v1/locks/proposedevice tokenPropose a tool definition
POST/api/v1/locks/{id}/approve · /rejectowner, adminDecide a pending lock
GET/api/v1/receiptsmemberSearch receipts
GET/api/v1/chains/{chain}/verifymemberRe-verify a device chain on the server
POST/api/v1/exportsowner, admin, auditorBuild an evidence export
GET/api/v1/exports/{id}owner, admin, auditorExport status and 60-second download links
GET/api/v1/advisoriespublicVerfy Advisories feed
Device tokens go in Authorization: Bearer atd_…. Session endpoints need the signed-in cookie and an org parameter.