Skip to main content

Ed25519 signed receipts + Cedar policy enforcement for Pydantic AI agents

Project description

scopeblind-pydantic-ai

PyPI version License: MIT Python 3.10+

Ed25519 signed receipts and (optional) Cedar policy enforcement for Pydantic AI agents. Every tool call produces a cryptographic receipt that anyone can verify offline with npx @veritasacta/verify.

Why

Pydantic AI agents call tools. Most teams log those calls to stdout, a SaaS dashboard, or nothing at all. When an auditor or regulator asks "prove what the agent did on April 12", logs aren't proof — they're mutable text files held by the operator.

This package wraps every tool call as a Pydantic AI Capability. The capability:

  1. Evaluates a policy (optional — Cedar, custom function, or allow-all)
  2. Signs the decision with Ed25519 (RFC 8032)
  3. Canonicalizes with JCS (RFC 8785) so the signature verifies deterministically across implementations
  4. Hash-chains each receipt to the previous one so insertion/deletion is detectable
  5. Exports to JSONL compatible with @veritasacta/verify — no ScopeBlind account, no network call, works air-gapped

Install

pip install scopeblind-pydantic-ai

Quick start

from pydantic_ai import Agent
from scopeblind_pydantic_ai import ScopeBlindSigning, ReceiptSigner

# Generate or load an Ed25519 signing key
signer = ReceiptSigner.generate()
signer.save_key("./keys/agent.json")

# Attach the capability to your agent
signing = ScopeBlindSigning(signer=signer, agent_name="weather-bot")

agent = Agent(
    "anthropic:claude-sonnet-4-6",
    capabilities=[signing],
    tools=[get_weather, get_forecast],
)

result = await agent.run("What's the weather in Sydney?")

# Inspect and export receipts
print(f"{signing.receipt_count} tool calls signed")
print(f"Public key for verification: {signing.public_key_hex}")
signing.export_receipts("./receipts.jsonl")

Verify the output:

npx @veritasacta/verify receipts.jsonl --key $(jq -r .public_key keys/agent.json)

Exit 0 = all receipts valid. Exit 1 = tampered. Exit 2 = malformed.

With a policy

The policy argument is a callable (tool_name, args) -> (allowed, deny_reason):

def no_production_writes(tool_name: str, args: dict) -> tuple[bool, str | None]:
    if tool_name == "database_write" and args.get("env") == "production":
        return (False, "writes to production require human approval")
    return (True, None)

signing = ScopeBlindSigning(signer=signer, policy=no_production_writes)

Denied calls raise SkipToolExecution, and a decision="deny" receipt is still signed — the record survives the block.

With Cedar

For declarative policy, run Cedar evaluation in your policy callable. Two options:

Option 1 — use protect-mcp's Cedar evaluator (shells out to the CLI):

import subprocess, json

def cedar_gate(tool_name: str, args: dict) -> tuple[bool, str | None]:
    result = subprocess.run(
        ["npx", "protect-mcp", "evaluate",
         "--policy", "./protect.cedar",
         "--tool", tool_name,
         "--input", json.dumps(args)],
        capture_output=True,
    )
    if result.returncode == 0:
        return (True, None)
    return (False, result.stderr.decode().strip() or "Cedar deny")

Option 2 — use the Cedar WASM bindings from cedar-policy/cedar-for-agents (merged #64) through a Python binding. Native Python Cedar bindings are on the way.

Receipt format

Every receipt is a JSON object following IETF draft-farley-acta-signed-receipts:

{
  "payload": {
    "type": "protectmcp:decision",
    "spec": "draft-farley-acta-signed-receipts-01",
    "tool_name": "get_weather",
    "tool_input_hash": "sha256:a3f8...",
    "decision": "allow",
    "issued_at": "2026-04-15T10:30:00.000Z",
    "issuer_id": "scopeblind-pydantic-ai",
    "session_id": "sess_3a8b2c",
    "sequence": 1,
    "previousReceiptHash": null,
    "agent_name": "weather-bot",
    "output_hash": "sha256:b7e2..."
  },
  "signature": {
    "alg": "EdDSA",
    "kid": "sb:pydai:bd84c36b620c",
    "sig": "4cde814b..."
  }
}

The signature covers the JCS-canonicalized payload, so any edit to any payload field breaks verification.

Ecosystem

Cross-compatible with the rest of the ScopeBlind / Veritas Acta stack. A receipt signed by this package verifies identically to one signed by:

  • protect-mcp (Node, Claude Code hooks)
  • protect-mcp-adk (Google ADK)
  • The Rust gateway (APS ProxyGateway)
  • The Microsoft Agent Governance Toolkit adapter (PR #667, merged)

Compatibility

Pydantic AI Status
>= 1.0.0 Supported — uses the Capability API (wrap_tool_execute hook)
< 1.0 Not supported — the hook API landed in the 1.0 release

Development

git clone https://github.com/ScopeBlind/scopeblind-gateway
cd scopeblind-gateway/packages/scopeblind-pydantic-ai
pip install -e ".[dev]"
pytest tests/ -v

Standards

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

scopeblind_pydantic_ai-0.1.0.tar.gz (11.0 kB view details)

Uploaded Source

Built Distribution

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

scopeblind_pydantic_ai-0.1.0-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file scopeblind_pydantic_ai-0.1.0.tar.gz.

File metadata

  • Download URL: scopeblind_pydantic_ai-0.1.0.tar.gz
  • Upload date:
  • Size: 11.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.6

File hashes

Hashes for scopeblind_pydantic_ai-0.1.0.tar.gz
Algorithm Hash digest
SHA256 af20953e07ad39575166a505344ee4eecf0991590d76cad5c1dee556bb97697c
MD5 fa4330289dda9064d447b101a4cd55c8
BLAKE2b-256 d4403aac7c877ddb8f78a5b63dce5ae6f0be452fee9856f9e834b36b450cefea

See more details on using hashes here.

File details

Details for the file scopeblind_pydantic_ai-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for scopeblind_pydantic_ai-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1607383a065d07ddd2737c6c73a81841caf8f79708992f133273ab33b773170c
MD5 dc34f08eecf07e921713aa281c375d8a
BLAKE2b-256 6bba7d0e4efebe623c052659c3447409cb9e032bf28882fb4a7e5ac476c7dd26

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