Ed25519 signed receipts + Cedar policy enforcement for Pydantic AI agents
Project description
scopeblind-pydantic-ai
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:
- Evaluates a policy (optional — Cedar, custom function, or allow-all)
- Signs the decision with Ed25519 (RFC 8032)
- Canonicalizes with JCS (RFC 8785) so the signature verifies deterministically across implementations
- Hash-chains each receipt to the previous one so insertion/deletion is detectable
- 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
- Ed25519 — RFC 8032
- JCS — RFC 8785
- Cedar — AWS's open authorization engine
- IETF Internet-Draft — draft-farley-acta-signed-receipts
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af20953e07ad39575166a505344ee4eecf0991590d76cad5c1dee556bb97697c
|
|
| MD5 |
fa4330289dda9064d447b101a4cd55c8
|
|
| BLAKE2b-256 |
d4403aac7c877ddb8f78a5b63dce5ae6f0be452fee9856f9e834b36b450cefea
|
File details
Details for the file scopeblind_pydantic_ai-0.1.0-py3-none-any.whl.
File metadata
- Download URL: scopeblind_pydantic_ai-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1607383a065d07ddd2737c6c73a81841caf8f79708992f133273ab33b773170c
|
|
| MD5 |
dc34f08eecf07e921713aa281c375d8a
|
|
| BLAKE2b-256 |
6bba7d0e4efebe623c052659c3447409cb9e032bf28882fb4a7e5ac476c7dd26
|