Skip to main content

Independent verifier for Keel governance evidence

Reason this release was yanked:

local-build source drift: __init__.py and _release_manifest.json were stale at twine time; functional contents were v2.4.0. Superseded by v2.4.2.

Project description

keel-verifier

PyPI Python CI License: MIT

Independent verifier for Permit-spec governance evidence and Keel audit exports.

Why this exists

Most AI platforms can tell you what they logged.

This verifier detects post-signing tampering of exported governance evidence. An auditor, customer, regulator, or security team can independently verify decisions, dispatched requests, returned responses, and lifecycle evidence integrity.

Verification runs locally and does not require access to Keel's systems.

What this verifier can and cannot prove

The verifier proves exported evidence has not been altered after signing. Like any signing system, the trust boundary includes the signer at signing-time. Defending against privileged signing-time manipulation requires a higher-assurance signing architecture beyond the scope of this verifier.

In practical terms:

  • Post-signing tampering of any element in the permit lifecycle (input, dispatch, provider response, client response, closure record) is detected — see the Tampering Detection Matrix in the online documentation.
  • Pre-signing manipulation by a privileged Keel operator at the moment evidence is created is NOT detected by this verifier. Defending against that threat model requires hardware-backed signing (TEE/HSM), a separate capability tier above what this verifier covers.

Quick Start

python -m pip install keel-verifier
keel-verify export --help

From a checkout:

python -m pip install -e .
python -m keel_verifier --help

The v0.2.0 invocation pattern still works:

python -m keel_verifier sample/export.json --self-attested

Common Commands

Task Command
Verify a signed export keel-verify export export.json manifest.json
Walk lifecycle chain entries keel-verify export export.json manifest.json --walk-events
Verify closure records keel-verify export export.json manifest.json --walk-events --verify-closure
Verify a checkpoint keel-verify checkpoint checkpoint.json
Verify a registered claim keel-verify claim delegation_denied_correctly --evidence-file evidence.json
Refresh cached trust roots keel-verify refresh-keys
Verify the installed wheel keel-verify self-check

What It Verifies

keel-verify export verifies a signed compliance export in four layers:

  1. The export bytes match the signed manifest content_hash.
  2. The manifest Ed25519 signature verifies against a trusted key.
  3. Workflow evidence siblings and incident bundle workflow files are verified when present.
  4. Optional Phase C/D checks walk bundled chain entries and verify closure records.

keel-verify checkpoint verifies integrity checkpoint JSON artifacts: the chain_heads composite hash, the Ed25519 checkpoint signature, and an embedded RFC 3161 timestamp MessageImprint when present.

keel-verify claim adjudicates pack-pinned evidence packs against the verifier's claim registry — see Claim Verification below.

keel-verify self-check verifies the installed wheel form of keel-verifier against the signed release artifact. It verifies the Sigstore-signed release manifest (full keyless signature and certificate-chain), the Rekor inclusion proof, the DigiCert and GlobalSign RFC 3161 TSA witnesses (bind-level: the receipts bind to the signed manifest hash and report granted status; full CMS signature and cert-chain validation against TSA trust roots is opt-in via the --tsa-ca-bundle extension and is not part of the default self-check), the embedded manifest's RFC 8785 JCS binding, and the wheel package files listed in the embedded manifest. It does not claim binary or OCI verification.

Installed Wheel Self-Check

Run self-check after installing from PyPI:

python -m pip install keel-verifier
keel-verify self-check

Successful output is wheel-scoped:

PASS: keel-verifier self-check passed for installed wheel form
  [OK] form: wheel form selected
  [OK] embedded_manifest: embedded release manifest is present and cycle-safe
  [OK] fetch: release manifest, signature, and TSA sidecar loaded
  [OK] sigstore_signature: signed release manifest verifies against expected GitHub Actions identity
  [OK] rekor_inclusion: Rekor inclusion proof is present and verified by sigstore-python
  [OK] tsa_witnesses: DigiCert and GlobalSign RFC 3161 receipts witness the manifest hash (bind-level; cert-chain validation is opt-in)
  [OK] embedded_binding: embedded manifest JCS hash matches signed release manifest binding
  [OK] per_file_digests: installed wheel files match embedded per-file digests

Failure output includes a stable error code:

FAILED: keel-verifier self-check failed for installed wheel form
  [FAIL] per_file_digests: SELF_CHECK_FILE_DIGEST_MISMATCH: installed file digest mismatch: keel_verifier/__init__.py

Self-check fetches release provenance online by default and uses a 24 hour cache under ~/.cache/keel-verifier/. Use --offline to require cached provenance, --no-cache to fetch without reading or writing cache entries, and --json for machine-readable stage results.

Obtaining a Signed Export

Request an audit export from the Keel compliance export API and include chain entries when you want full lifecycle walking:

curl -sS -X POST "https://api.keelapi.com/v1/compliance/exports?include_chain_entries=true" \
  -H "Authorization: Bearer $KEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id":"<project_uuid>","format":"json"}'

Download both artifacts returned by the export workflow:

  • the export payload, for example export.json
  • the signed manifest, for example manifest.json

Then run:

keel-verify export export.json manifest.json --walk-events --verify-closure

The flag form used by the internal verifier is also supported:

keel-verify export --export-file export.json --manifest manifest.json --walk-events --verify-closure

Export manifests must be signed by default. Legacy unsigned manifests fail closed after the content-hash check. Use --allow-unsigned only for archaeology or local fixtures where content-hash consistency is useful but issuer authenticity is intentionally out of scope:

keel-verify export export.json manifest.json --allow-unsigned

Chain Walking

--walk-events parses audit_export_bundle files with schema_version=2 and include_chain_entries=true.

It groups entries by chain_scope, sorts by sequence_number, recomputes every record_hash, verifies prev_hash continuity inside the export window, and fails closed on unknown chain_format_version values.

Schema version 1 exports remain backward compatible. They can still be verified at the export-signature layer, but they do not contain chain entries to walk.

Closure Verification

--verify-closure verifies permit.closed entries.

For closure_v1, it verifies the closure Ed25519 signature and cross-references provider/client response digests against the bundled lifecycle events.

For closure_v2, it also verifies dispatch_request_digest_v1 against the permit's binding_request_hash, verifying that the dispatch-time request body is the one covered by the closure record.

Closure verification uses public keys with purpose permit_binding_signing. Pass a manifest explicitly when needed:

keel-verify export export.json manifest.json \
  --key-manifest permit-binding-keys.json \
  --walk-events \
  --verify-closure

The bundled trust root lives at keel_verifier/data/trust_root.json. It includes the production export and checkpoint signing keys currently served by https://api.keelapi.com/v1/compliance/keys, plus the production permit-binding key served by https://api.keelapi.com/v1/integrity/permit-binding-public-keys.

Workflow Intent Verification

keel-verify export understands keel.vanta.workflow_evidence/v1 artifacts emitted alongside Vanta evidence exports. When the signed export manifest includes a sibling_artifacts.workflow_evidence entry, the verifier checks the sibling file hash, export signature, workflow declaration signatures, workflow amendment signatures, amendment version ordering, declaration effective_intent_hash, and any permit workflow_state_json snapshots in the main evidence.

Incident evidence zip bundles remain backward compatible. Manifest version 1 bundles without workflow files verify as before. Manifest version 2 bundles must include workflow_declarations.jsonl and workflow_amendments.jsonl; the verifier validates those files and fails gracefully on unknown manifest versions.

Claim Verification (Pack-Pinned Semantics)

v2.0.0 adds pack-pinned semantic verification: an evidence pack can declare which semantic artifacts it was emitted under (by (id, sha256)), and the verifier reproduces those exact semantics from a permanent, append-only allowlist. A version-pinned pack receives reproducible adjudication — any future verifier release must resolve those exact pinned semantics and reach the same claim verdicts, or explicitly decline. It never silently reinterprets a prior pinned claim.

For closure.dispatch_binding.v1, streaming dispatch paths emit separate provider.response.received and client.response.delivered events as digest carriers; non-streaming dispatch paths emit execution.completed as the accepted digest carrier. Both shapes carry provider_response_digest_v1 and client_response_digest_v1 and are equivalently adjudicated by the verifier.

keel-verify claim delegation_denied_correctly --evidence-file evidence.json

Claim output is JSON by default. --json is accepted for consistency with the export and checkpoint commands.

A pack carries two manifest blocks:

  • claim_set — which claims the pack asserts, each marked required: true|false.
  • semantics_pins — which semantic artifacts (by (id, sha256)) the verifier should resolve.

The verifier resolves and validates each pin against the permanent allowlist, then adjudicates each declared claim.

Structured per-claim verdicts

--json output gains a claims array with per-claim verdicts using a four-value enum:

verdict meaning
supported The claim is positively established by the evidence.
disproved The evidence contradicts the claim.
insufficient_evidence The pack doesn't carry enough to decide.
unverifiable_scope The claim falls outside what the verifier is in scope to decide.

For a pinned pack, every claim the claim_set marks required must receive supported for the pack's overall ok to be true. Legacy un-pinned evidence (no claim_set / semantics_pins) is evaluated under the permanent keel.pre_pinning_default.v0 profile and is not subject to required-claim enforcement — v1.x exports continue to verify unchanged.

Specs

TSA Trust Validation

Checkpoint verification checks embedded RFC 3161 timestamp receipts by confirming the TSA MessageImprint matches the checkpoint composite_hash.

For opt-in TSA authenticity validation, pass a CA bundle:

keel-verify checkpoint checkpoint.json --tsa-ca-bundle tsa-ca-bundle.pem

This uses OpenSSL 3.x to verify the CMS signature, certificate chain, and timestamping purpose against the supplied CA bundle. It does not check historical revocation status at the timestamp issuance time.

Tampering Matrix

The verifier emits stable WALK_* failure codes, including:

  • WALK_RECORD_HASH_MISMATCH
  • WALK_PREV_HASH_DISCONTINUITY
  • WALK_SEQUENCE_INVERSION
  • WALK_UNKNOWN_CHAIN_FORMAT
  • WALK_CLOSURE_SIGNATURE_INVALID
  • WALK_CLOSURE_DIGEST_MISMATCH
  • WALK_CLOSURE_DIGEST_MISSING
  • WALK_CLOSURE_DISPATCH_DIGEST_MISMATCH
  • WALK_UNKNOWN_CLOSURE_FORMAT

Example: if a provider response is modified after signing, verification fails with WALK_CLOSURE_DIGEST_MISMATCH.

The authoritative matrix is maintained in the online documentation: https://docs.keelapi.com/12-tampering-detection-matrix

Trust Model

There are two useful kinds of verification:

  • Self-attested: the file agrees with itself. This verifies internal consistency only.
  • Trust-root verified: the artifact verifies against a key you trust, such as the bundled production trust root, a pinned public key, or a manifest fetched and saved out-of-band.

Trust sources, strongest first:

Mode Flags Notes
Pinned key --expected-public-key ed25519:... or --public-key ed25519:... Strongest when obtained out-of-band.
Key manifest --key-manifest keys.json Supports key rotation and active windows.
Key manifest URL --key-manifest-url URL Explicit network fetch.
Cached manifest none (set up via keel-verify refresh-keys) Default once cache exists. Lives at ~/.keel-verifier/trust-root.json.
Bundled trust root none Always-present floor. No phone-home.
Self-attested --self-attested Development/sample mode only.

--public-key-url is also supported for checkpoint verification against the single live checkpoint public-key endpoint.

When no flag is passed, the verifier resolves the trust root in this order: explicit --key-manifest[-url] → cached ~/.keel-verifier/trust-root.json (if present) → wheel-bundled data/trust_root.json.

Refreshing trust roots after key rotation

The wheel ships a snapshot of the trust root from build time. After a key rotation, a wheel published before the rotation will not verify post-rotation artifacts out of the box. Three resolutions:

  1. pip install --upgrade keel-verifier — pulls the latest bundled snapshot.
  2. keel-verify refresh-keys — fetches a fresh manifest from any trust-root channel and caches it at ~/.keel-verifier/trust-root.json. The verifier prefers the cache over the bundled snapshot on subsequent runs.
  3. Pin a manifest at audit time: download the manifest alongside the artifact, pass it explicitly with --key-manifest <archived-file>.

refresh-keys flags:

keel-verify refresh-keys                  # auto: try Keel API, then GitHub
keel-verify refresh-keys --source api     # only try the Keel API
keel-verify refresh-keys --source github  # only try the GitHub mirror

CLI Examples

keel-verify export export.json manifest.json
keel-verify export export.json manifest.json --walk-events
keel-verify export export.json manifest.json --walk-events --verify-closure
keel-verify export export.json manifest.json --allow-unsigned
keel-verify checkpoint checkpoint.json
keel-verify claim delegation_denied_correctly --evidence-file evidence.json
keel-verify claim delegation_denied_correctly --evidence-file evidence.json --json
keel-verify refresh-keys
keel-verify refresh-keys --source github
python -m keel_verifier sample/export.json --self-attested
python -m keel_verifier sample/export.json --json --self-attested

Exit code 0 means verified. Exit code 1 means verification failed. Exit code 2 means bad usage.

Network Behavior

Normal verification does not phone home. Network fetches happen only when you run keel-verify refresh-keys or pass an explicit URL trust-root flag such as --public-key-url or --key-manifest-url.

The repository CI workflows also contact live Keel endpoints to detect bundled trust-root drift.

There is no telemetry.

Library Use

import json
from pathlib import Path

from keel_verifier import (
    verify,
    verify_delegation_denied_correctly,
)

result = verify("sample/export.json", self_attested=True)
if not result.ok:
    raise SystemExit(result.error)

evidence = json.loads(Path("evidence.json").read_text())
claim = verify_delegation_denied_correctly(evidence, include_semantics=True)
if claim["status"] != "supported":
    raise SystemExit(claim)

Versioning

v2.0.0 introduces pack-pinned semantics and structured claim verdicts. The documented CLI invocation patterns still work, including python -m keel_verifier <artifact>.

Related Projects

Maintainer

Maintained by Keel API, Inc.

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keel_verifier-2.4.1.tar.gz (181.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keel_verifier-2.4.1-py3-none-any.whl (149.4 kB view details)

Uploaded Python 3

File details

Details for the file keel_verifier-2.4.1.tar.gz.

File metadata

  • Download URL: keel_verifier-2.4.1.tar.gz
  • Upload date:
  • Size: 181.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for keel_verifier-2.4.1.tar.gz
Algorithm Hash digest
SHA256 1a49043b1346cce6a546c3419831f99e702a3a415b98e67760c14162a0227661
MD5 9287f6d9d46677c09d13b15efa3b80d2
BLAKE2b-256 7a08c1d970718e6e13ac9ae754d113dd6c4c9b3b2682cf0ac8c5a104283807ca

See more details on using hashes here.

File details

Details for the file keel_verifier-2.4.1-py3-none-any.whl.

File metadata

  • Download URL: keel_verifier-2.4.1-py3-none-any.whl
  • Upload date:
  • Size: 149.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for keel_verifier-2.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 72cba7f7f23c582c6a27d5f3ba84c6314b9ffdd5d941b5f93e12d524fc0ce948
MD5 7bfb878cd001ff8f4ab8e0fa3fcb5914
BLAKE2b-256 3395d65a34a1151ec2869602e465013baf743a7805d3ea4e56d326b1dffa2680

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page