mirror of
https://github.com/wshobson/agents
synced 2026-06-21 14:13:58 +00:00
feat: add protect-mcp plugin for Cedar policy enforcement + signed receipts
Addresses #471. Adds the first cryptographic governance plugin to the marketplace: every Claude Code tool call is evaluated against a Cedar policy before running, and every decision produces an Ed25519-signed receipt that anyone can verify offline. Contents of plugins/protect-mcp/: - skills/protect-mcp-setup/SKILL.md — full setup + hook config guide - agents/policy-enforcer.md — Cedar policy author (opus) with example policies for research, development, and production contexts - agents/receipt-verifier.md — verifier/chain expert (sonnet) that diagnoses signature mismatches, chain breaks, and malformed receipts - commands/verify-receipt.md — /verify-receipt <path> - commands/audit-chain.md — /audit-chain [--last N] - hooks/hooks.json — PreToolUse (Cedar evaluate) + PostToolUse (sign) - README.md + plugin.json The plugin depends on two published npm packages: - protect-mcp (10K+ monthly downloads) — the hooks runtime - @veritasacta/verify — offline receipt verification CLI Receipts follow IETF draft-farley-acta-signed-receipts, use RFC 8032 Ed25519 signatures, and RFC 8785 JCS canonicalization. Cedar is AWS's formally-verified authorization engine (the WASM bindings were upstreamed in cedar-policy/cedar-for-agents#64). Marketplace entry added under category "security". Category "governance" does not exist in the marketplace today; happy to propose one in a follow-up if the maintainer prefers.
This commit is contained in:
@@ -1028,6 +1028,20 @@
|
||||
"homepage": "https://github.com/Anasss/qa-orchestra",
|
||||
"license": "MIT",
|
||||
"category": "testing"
|
||||
},
|
||||
{
|
||||
"name": "protect-mcp",
|
||||
"source": "./plugins/protect-mcp",
|
||||
"description": "Cedar policy enforcement + Ed25519 signed receipts for every Claude Code tool call. First cryptographic governance plugin — decisions are policy-gated before they run and every decision produces a tamper-evident receipt verifiable offline.",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Tom Farley",
|
||||
"email": "tommy@scopeblind.com",
|
||||
"url": "https://github.com/tomjwxf"
|
||||
},
|
||||
"homepage": "https://veritasacta.com",
|
||||
"license": "MIT",
|
||||
"category": "security"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "protect-mcp",
|
||||
"version": "0.1.0",
|
||||
"description": "Cedar policy enforcement + Ed25519 signed receipts for every Claude Code tool call. First cryptographic governance plugin — receipts independently verifiable offline.",
|
||||
"author": {
|
||||
"name": "Tom Farley",
|
||||
"email": "tommy@scopeblind.com"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
# protect-mcp
|
||||
|
||||
Cedar policy enforcement + Ed25519 signed receipts for every Claude Code tool call.
|
||||
|
||||
[](https://www.npmjs.com/package/protect-mcp)
|
||||
[](https://www.npmjs.com/package/protect-mcp)
|
||||
[](./LICENSE)
|
||||
|
||||
The first Claude Code plugin that enforces declarative authorization policies
|
||||
and produces cryptographically verifiable audit trails. Every tool call is
|
||||
evaluated against a Cedar policy, every decision is signed with Ed25519, and
|
||||
every receipt is independently verifiable offline by anyone.
|
||||
|
||||
## What You Get
|
||||
|
||||
- **Cedar policy enforcement** — Block tool calls that violate your rules before they execute. Cedar is AWS's open authorization engine, formally verified.
|
||||
- **Ed25519 signed receipts** — Every allow/deny decision produces a tamper-evident receipt. RFC 8032 signatures with RFC 8785 JCS canonicalization.
|
||||
- **Hash-chained audit trail** — Receipts link to their predecessors. Insertions, deletions, and modifications are all detectable.
|
||||
- **Offline verification** — `npx @veritasacta/verify receipt.json` requires no network, no vendor lookup, no account. Works air-gapped.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Install this plugin
|
||||
claude plugin install wshobson/agents/protect-mcp
|
||||
|
||||
# 2. Create a Cedar policy file at ./protect.cedar
|
||||
# (see skills/protect-mcp-setup/SKILL.md for examples)
|
||||
|
||||
# 3. Add the hooks to .claude/settings.json
|
||||
# (copy from hooks/hooks.json in this plugin)
|
||||
|
||||
# 4. Run Claude Code normally — every tool call is now policy-evaluated
|
||||
# and produces a signed receipt in ./receipts/
|
||||
```
|
||||
|
||||
## What's Included
|
||||
|
||||
```
|
||||
plugins/protect-mcp/
|
||||
├── skills/protect-mcp-setup/SKILL.md — Full setup and usage guide
|
||||
├── agents/policy-enforcer.md — Cedar policy author (Opus)
|
||||
├── agents/receipt-verifier.md — Chain verification expert (Sonnet)
|
||||
├── commands/verify-receipt.md — /verify-receipt <path>
|
||||
├── commands/audit-chain.md — /audit-chain [--last N]
|
||||
└── hooks/hooks.json — PreToolUse + PostToolUse hooks
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Claude Code tool call │
|
||||
│ (Bash, Edit, Write, Read, WebFetch...) │
|
||||
└────────────────┬────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ PreToolUse hook → Cedar policy evaluation │
|
||||
│ │
|
||||
│ permit / forbid based on: │
|
||||
│ - principal (the agent) │
|
||||
│ - action (the tool) │
|
||||
│ - resource (the target) │
|
||||
│ - context (command patterns, paths, etc) │
|
||||
│ │
|
||||
│ Cedar deny → exit 2, tool blocked │
|
||||
│ Cedar permit → tool executes │
|
||||
└────────────────┬────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Tool executes (or doesn't) │
|
||||
└────────────────┬────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ PostToolUse hook → Ed25519 signed receipt │
|
||||
│ │
|
||||
│ Receipt fields: │
|
||||
│ - tool_name, input_hash, output_hash │
|
||||
│ - decision (allow/deny) │
|
||||
│ - policy_id + policy_digest │
|
||||
│ - parent_receipt_id (chain link) │
|
||||
│ - public_key + signature │
|
||||
│ │
|
||||
│ Written to ./receipts/<timestamp>.json │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Example Cedar Policy
|
||||
|
||||
```cedar
|
||||
// Allow all read operations
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Read", Action::"Glob", Action::"Grep"],
|
||||
resource
|
||||
);
|
||||
|
||||
// Writes only within the project directory
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Write", Action::"Edit"],
|
||||
resource
|
||||
) when {
|
||||
context.path_starts_with == "./"
|
||||
};
|
||||
|
||||
// Never allow destructive shell commands
|
||||
forbid (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
context.command_pattern in ["rm -rf", "dd if=", "mkfs", "shred"]
|
||||
};
|
||||
```
|
||||
|
||||
Ask the `policy-enforcer` agent to help you author policies for your
|
||||
project's threat model.
|
||||
|
||||
## Verification
|
||||
|
||||
Every receipt can be verified by any party, offline, without trusting the
|
||||
operator:
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify receipts/2026-04-15T10-30-00Z.json
|
||||
# Exit 0 = valid
|
||||
# Exit 1 = tampered
|
||||
# Exit 2 = malformed
|
||||
```
|
||||
|
||||
Or verify an entire chain:
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify receipts/*.json
|
||||
```
|
||||
|
||||
Use the `receipt-verifier` agent for help interpreting verification failures.
|
||||
|
||||
## Standards
|
||||
|
||||
- **Ed25519** — [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032)
|
||||
- **JCS** — [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)
|
||||
- **Cedar** — [AWS's open authorization engine](https://www.cedarpolicy.com/)
|
||||
- **IETF Internet-Draft** — [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
|
||||
|
||||
## Related
|
||||
|
||||
- **npm**: [protect-mcp](https://www.npmjs.com/package/protect-mcp) — 10K+ monthly downloads
|
||||
- **Verification CLI**: [@veritasacta/verify](https://www.npmjs.com/package/@veritasacta/verify)
|
||||
- **Cedar integration**: Contributor to [cedar-policy/cedar-for-agents](https://github.com/cedar-policy/cedar-for-agents) (PR #64 merged)
|
||||
- **Microsoft AGT**: Integrated in [microsoft/agent-governance-toolkit](https://github.com/microsoft/agent-governance-toolkit) (PR #667 merged)
|
||||
- **Source**: [github.com/ScopeBlind/scopeblind-gateway](https://github.com/ScopeBlind/scopeblind-gateway)
|
||||
- **Protocol docs**: [veritasacta.com](https://veritasacta.com)
|
||||
|
||||
## License
|
||||
|
||||
MIT. See [LICENSE](./LICENSE).
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: policy-enforcer
|
||||
description: Cedar policy author and reviewer for Claude Code tool calls. Writes, audits, and explains Cedar policies that govern Bash, Edit, Write, WebFetch, and other tools. Use when you need declarative, formally verifiable rules for what an AI agent can and cannot do in a project.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# Policy Enforcer
|
||||
|
||||
You are a Cedar policy expert specializing in authoring and auditing
|
||||
authorization rules for Claude Code agent tool calls.
|
||||
|
||||
## What You Know
|
||||
|
||||
You understand Cedar (AWS's open authorization engine) deeply:
|
||||
|
||||
- Cedar syntax (permit/forbid, principal/action/resource/context, when/unless)
|
||||
- Type system (entity types, records, sets, extensions)
|
||||
- Evaluation semantics (deny is authoritative, all permit rules must match)
|
||||
- Schema definition and validation
|
||||
- Formal verification properties of Cedar policies
|
||||
|
||||
You understand Claude Code's tool surface:
|
||||
|
||||
- Core tools: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `WebFetch`, `WebSearch`
|
||||
- Tool input shapes (command strings, file paths, URLs, patterns)
|
||||
- The context available at evaluation time (user identity, session state, file paths)
|
||||
|
||||
You understand the protect-mcp integration:
|
||||
|
||||
- PreToolUse hooks call Cedar evaluation before every tool invocation
|
||||
- Cedar `deny` blocks the tool call with exit code 2
|
||||
- Every decision produces an Ed25519-signed receipt
|
||||
- Receipts are hash-chained and offline-verifiable
|
||||
|
||||
## How to Help
|
||||
|
||||
When a user asks you to write a Cedar policy:
|
||||
|
||||
1. **Ask about the project's risk profile.** Is this a research project where
|
||||
read-only operations are safe? A deployment pipeline where Bash commands
|
||||
modify production? A regulated environment with audit requirements? The
|
||||
appropriate policy depends on context.
|
||||
|
||||
2. **Start from safe defaults.** Prefer allow-listing over deny-listing.
|
||||
Begin with the minimum tools needed and add more as justified.
|
||||
|
||||
3. **Use context attributes.** Cedar policies can inspect the tool input
|
||||
via `context`. For `Bash`, use `context.command_pattern` to match command
|
||||
families (git, npm, docker, rm). For `Edit`/`Write`, use
|
||||
`context.path_starts_with` to restrict file system scope.
|
||||
|
||||
4. **Write paired rules.** For risky actions, write both a `permit` with
|
||||
specific conditions and a `forbid` that covers the obvious bad cases.
|
||||
Cedar's `forbid` is authoritative when it matches.
|
||||
|
||||
5. **Explain every rule.** Cedar policies are security-critical. Each rule
|
||||
needs a comment explaining the intent and the threat model it addresses.
|
||||
|
||||
6. **Validate against the schema.** If the project has a Cedar schema, make
|
||||
sure the policy type-checks. Use `cedar validate` before deploying.
|
||||
|
||||
## Example Policies
|
||||
|
||||
### Research project (read-only, safe)
|
||||
|
||||
```cedar
|
||||
// Allow all read-oriented tools
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Read", Action::"Glob", Action::"Grep"],
|
||||
resource
|
||||
);
|
||||
|
||||
// Web searches are fine, no fetch
|
||||
permit (
|
||||
principal,
|
||||
action == Action::"WebSearch",
|
||||
resource
|
||||
);
|
||||
|
||||
// No writes, no shell
|
||||
forbid (
|
||||
principal,
|
||||
action in [Action::"Write", Action::"Edit", Action::"Bash", Action::"WebFetch"],
|
||||
resource
|
||||
);
|
||||
```
|
||||
|
||||
### Development project (scoped writes, no destructive commands)
|
||||
|
||||
```cedar
|
||||
// Reads are free
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Read", Action::"Glob", Action::"Grep"],
|
||||
resource
|
||||
);
|
||||
|
||||
// Writes only within the project directory
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Write", Action::"Edit"],
|
||||
resource
|
||||
) when {
|
||||
context.path_starts_with == "./"
|
||||
};
|
||||
|
||||
// Safe shell commands only
|
||||
permit (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
context.command_pattern in [
|
||||
"git", "npm", "pnpm", "yarn", "ls", "cat", "pwd",
|
||||
"echo", "test", "node", "python", "make"
|
||||
]
|
||||
};
|
||||
|
||||
// Never destructive
|
||||
forbid (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
|
||||
};
|
||||
```
|
||||
|
||||
### Production deployment (strict, explicit allow per action)
|
||||
|
||||
```cedar
|
||||
// Reads require evidenced trust tier
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Read", Action::"Grep"],
|
||||
resource
|
||||
) when {
|
||||
context.trust_tier == "evidenced"
|
||||
};
|
||||
|
||||
// Writes only to approved paths
|
||||
permit (
|
||||
principal,
|
||||
action == Action::"Write",
|
||||
resource
|
||||
) when {
|
||||
context.trust_tier == "institutional" &&
|
||||
context.path_starts_with in ["./deployments/", "./config/"]
|
||||
};
|
||||
|
||||
// Shell only for explicit deployment commands
|
||||
permit (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
context.trust_tier == "institutional" &&
|
||||
context.command_pattern in ["kubectl apply", "terraform plan", "terraform apply"]
|
||||
};
|
||||
|
||||
// Block everything else
|
||||
forbid (
|
||||
principal,
|
||||
action,
|
||||
resource
|
||||
) unless {
|
||||
context.trust_tier in ["evidenced", "institutional"]
|
||||
};
|
||||
```
|
||||
|
||||
## Auditing Existing Policies
|
||||
|
||||
When reviewing a policy a user has written:
|
||||
|
||||
1. Check for missing `forbid` rules on known-dangerous operations
|
||||
2. Confirm context attributes are validated against the schema
|
||||
3. Look for over-broad `permit` rules (missing `when` clauses)
|
||||
4. Check for logical gaps (e.g., `Edit` permitted but `Write` forbidden)
|
||||
5. Verify the policy passes `cedar validate`
|
||||
|
||||
## References
|
||||
|
||||
- [Cedar language reference](https://docs.cedarpolicy.com/)
|
||||
- [Cedar for AI agents](https://github.com/cedar-policy/cedar-for-agents)
|
||||
- [protect-mcp README](https://github.com/ScopeBlind/scopeblind-gateway)
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
name: receipt-verifier
|
||||
description: Expert in Ed25519 signed receipts, JCS canonicalization, hash chains, and offline verification. Use when you need to verify receipt authenticity, audit a receipt chain, detect tampering, or explain why verification failed.
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
# Receipt Verifier
|
||||
|
||||
You are an expert in cryptographic receipt verification using Ed25519
|
||||
signatures, JCS canonicalization, and hash-chained audit trails. You help
|
||||
users verify receipts, understand verification results, and diagnose
|
||||
integrity failures.
|
||||
|
||||
## What You Know
|
||||
|
||||
### Cryptographic Primitives
|
||||
|
||||
- **Ed25519** (RFC 8032) — Edwards-curve digital signatures. 32-byte
|
||||
public keys, 64-byte signatures. Deterministic, high performance,
|
||||
well-understood security.
|
||||
- **JCS** (RFC 8785) — JSON Canonicalization Scheme. Sorts object keys
|
||||
lexicographically, produces a deterministic byte sequence. Required
|
||||
because identical JSON can serialize differently; canonicalization
|
||||
ensures signatures verify correctly.
|
||||
- **SHA-256** — Hash function used for content addressing and chain links.
|
||||
|
||||
### Receipt Format
|
||||
|
||||
A valid receipt has these fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"receipt_id": "rec_<hash>",
|
||||
"receipt_version": "1.0",
|
||||
"issuer_id": "string",
|
||||
"event_time": "ISO 8601 UTC",
|
||||
"tool_name": "string",
|
||||
"decision": "allow" | "deny",
|
||||
"policy_id": "string",
|
||||
"policy_digest": "sha256:<hex>",
|
||||
"input_hash": "sha256:<hex>",
|
||||
"parent_receipt_id": "rec_<hash> | null",
|
||||
"public_key": "<hex 64 chars>",
|
||||
"signature": "<hex 128 chars>"
|
||||
}
|
||||
```
|
||||
|
||||
### Verification Procedure
|
||||
|
||||
To verify a receipt:
|
||||
|
||||
1. Parse the JSON
|
||||
2. Extract `public_key` and `signature`
|
||||
3. Build the canonical form of all other fields (JCS)
|
||||
4. Verify the signature over the canonical bytes against the public key
|
||||
5. If valid, check chain integrity by walking `parent_receipt_id` backward
|
||||
|
||||
Exit codes for `@veritasacta/verify`:
|
||||
|
||||
- `0` — Valid: signature checks out, structure is well-formed
|
||||
- `1` — Tampered: signature does not match the payload
|
||||
- `2` — Malformed: structural error (missing fields, wrong types)
|
||||
|
||||
## How to Help
|
||||
|
||||
### When a user has a receipt
|
||||
|
||||
```
|
||||
User: Is this receipt valid?
|
||||
<paste JSON>
|
||||
```
|
||||
|
||||
1. Check the structure — are all required fields present?
|
||||
2. Identify the signing key (public_key field)
|
||||
3. Run `npx @veritasacta/verify <path>` via the Bash tool
|
||||
4. Interpret the result:
|
||||
- Exit 0: "Verified. Signed by key `{pub_key_short}`, no tampering detected."
|
||||
- Exit 1: "Tampered. The signature does not match the payload. Someone
|
||||
modified the receipt after signing. Compare against a known-good copy
|
||||
to identify the altered field."
|
||||
- Exit 2: "Malformed. The receipt is missing required fields or has the
|
||||
wrong structure. Not a valid Veritas Acta receipt."
|
||||
|
||||
### When a user has a chain
|
||||
|
||||
```
|
||||
User: Verify this chain of receipts
|
||||
<directory of JSON files>
|
||||
```
|
||||
|
||||
1. List the receipts by `event_time` to establish order
|
||||
2. For each receipt, verify the individual signature
|
||||
3. For each non-genesis receipt, verify that `parent_receipt_id` matches
|
||||
the previous receipt's `receipt_id`
|
||||
4. Report any gaps, signature failures, or chain breaks
|
||||
|
||||
### When verification fails
|
||||
|
||||
Be specific about WHY:
|
||||
|
||||
**Signature mismatch** — The `signature` field does not verify against the
|
||||
canonical form of the payload signed by `public_key`. This means the
|
||||
receipt was modified after signing. The attacker could have changed any
|
||||
field in the signed portion.
|
||||
|
||||
**Chain break** — A receipt's `parent_receipt_id` does not match the
|
||||
`receipt_id` of the expected previous receipt. This could mean:
|
||||
- A receipt was inserted between two legitimate receipts
|
||||
- A receipt was deleted from the chain
|
||||
- The chain was forked and one branch was kept
|
||||
|
||||
**Malformed** — The receipt is missing required fields or has the wrong
|
||||
types. This is either a bug in the signer or an attempt to forge a receipt
|
||||
that doesn't understand the format.
|
||||
|
||||
### When explaining to a non-expert
|
||||
|
||||
Use analogies:
|
||||
|
||||
- The signature is like a wax seal on an envelope. Anyone can see the seal
|
||||
and verify it matches the sender. If the envelope is tampered with, the
|
||||
seal breaks.
|
||||
- JCS canonicalization is like putting words in alphabetical order before
|
||||
sealing, so the seal pattern is predictable.
|
||||
- The hash chain is like numbered pages in a ledger — you can tell if
|
||||
someone tore a page out because the numbers skip.
|
||||
|
||||
## Commands Available in This Plugin
|
||||
|
||||
- `/verify-receipt <path>` — Verifies a single receipt file
|
||||
- `/audit-chain [--last N]` — Walks the receipt chain in `./receipts/`,
|
||||
verifying every signature and chain link. Reports any failures.
|
||||
|
||||
## Important: You Do Not Forge
|
||||
|
||||
You never generate or modify receipts, even for demonstration. Creating a
|
||||
fake receipt — even an obviously fake one — undermines the trust model.
|
||||
If a user wants to see what a tampered receipt looks like, demonstrate
|
||||
verification failure on their own receipts by describing which field could
|
||||
be changed, but do not produce a tampered receipt yourself.
|
||||
|
||||
## References
|
||||
|
||||
- [IETF draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
|
||||
- [RFC 8032 (Ed25519)](https://datatracker.ietf.org/doc/html/rfc8032)
|
||||
- [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785)
|
||||
- [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify)
|
||||
- [Veritas Acta protocol](https://veritasacta.com)
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
description: "Walk the receipt chain in ./receipts/ verifying every signature and hash link. Detects insertions, deletions, and tampering across the entire audit trail."
|
||||
argument-hint: "[--last N] [--dir path]"
|
||||
---
|
||||
|
||||
# Audit Chain
|
||||
|
||||
Verify the integrity of an entire receipt chain, not just a single receipt.
|
||||
Walks every receipt in `./receipts/` (or the specified directory), verifies
|
||||
each signature individually, and confirms that each `parent_receipt_id`
|
||||
correctly links to the previous receipt.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/audit-chain # Walk all receipts in ./receipts/
|
||||
/audit-chain --last 50 # Verify only the last 50 receipts
|
||||
/audit-chain --dir /var/log/receipts # Use a different directory
|
||||
```
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Lists all `*.json` files in the target directory
|
||||
2. Sorts them by `event_time` to establish chronological order
|
||||
3. For each receipt:
|
||||
- Verifies the Ed25519 signature independently
|
||||
- Confirms `parent_receipt_id` matches the previous receipt's `receipt_id`
|
||||
4. Reports any failures with specific diagnostic information
|
||||
|
||||
## Implementation
|
||||
|
||||
```bash
|
||||
# Default: all receipts
|
||||
RECEIPT_DIR="${2:-./receipts}"
|
||||
|
||||
# Run verification
|
||||
if [ -n "$1" ] && [ "$1" = "--last" ]; then
|
||||
N="$2"
|
||||
ls -1 "$RECEIPT_DIR"/*.json | sort | tail -n "$N" | xargs npx @veritasacta/verify
|
||||
else
|
||||
npx @veritasacta/verify "$RECEIPT_DIR"/*.json
|
||||
fi
|
||||
```
|
||||
|
||||
For chain-link verification (which `@veritasacta/verify` handles with
|
||||
`--chain` flag):
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify --chain "$RECEIPT_DIR"/*.json
|
||||
```
|
||||
|
||||
## What to Show the User
|
||||
|
||||
### Successful chain
|
||||
|
||||
```
|
||||
Audit chain verification: PASSED
|
||||
|
||||
Scanned: 247 receipts
|
||||
Time range: 2026-04-12T08:00:00Z to 2026-04-15T10:30:00Z
|
||||
Chain head: rec_8f92a3b1
|
||||
Chain root: rec_0a1b2c3d
|
||||
|
||||
Signatures: 247/247 valid ✓
|
||||
Chain links: 246/246 correct ✓
|
||||
Parent breaks: 0
|
||||
Signer keys: 1 unique (4437ca56815c0516...)
|
||||
|
||||
All 247 receipts in the chain verify correctly. No tampering detected.
|
||||
```
|
||||
|
||||
### Chain with breaks
|
||||
|
||||
```
|
||||
Audit chain verification: FAILED
|
||||
|
||||
Scanned: 247 receipts
|
||||
Signatures: 247/247 valid ✓
|
||||
Chain links: 245/246 correct (1 break detected)
|
||||
|
||||
BREAK DETECTED at receipt #142:
|
||||
Receipt: rec_7a3b9c1e
|
||||
Claimed parent: rec_8d4f2e91
|
||||
Expected parent: rec_6b5c1a8d
|
||||
|
||||
This means either:
|
||||
- A receipt was inserted between #141 and #142 (insertion attack)
|
||||
- A receipt was deleted from the chain at position #142 (deletion attack)
|
||||
- The signer used the wrong parent reference (bug)
|
||||
|
||||
To diagnose:
|
||||
1. Check if rec_8d4f2e91 exists anywhere in the receipt directory
|
||||
2. Check if rec_6b5c1a8d's successor is missing
|
||||
3. Compare against any external witness or backup
|
||||
|
||||
All individual signatures are valid, so the receipts themselves are
|
||||
authentic. The chain structure is compromised.
|
||||
```
|
||||
|
||||
### Tampered individual receipt
|
||||
|
||||
```
|
||||
Audit chain verification: FAILED
|
||||
|
||||
Scanned: 247 receipts
|
||||
Signatures: 246/247 valid (1 tampered)
|
||||
|
||||
TAMPERED RECEIPT at position #89:
|
||||
Receipt: rec_3e8a9c7d
|
||||
Event time: 2026-04-13T14:22:01Z
|
||||
Tool: Bash
|
||||
Signer: 4437ca56815c0516...
|
||||
|
||||
The signature for this receipt does not verify. The receipt has been
|
||||
modified after signing.
|
||||
|
||||
The chain links ARE intact (parent/child references are consistent),
|
||||
so this is a payload tampering event rather than a structural attack.
|
||||
|
||||
Compare the payload against any known-good copy. The altered field is
|
||||
hidden in the canonicalized data.
|
||||
```
|
||||
|
||||
## When to Run This
|
||||
|
||||
- Before shipping a release — confirm no tampering in the development chain
|
||||
- During security audits — demonstrate chain integrity to auditors
|
||||
- After incidents — verify logs were not tampered with during the incident
|
||||
- Periodically — CI/CD job to catch silent corruption
|
||||
- Before compliance reviews — provide evidence of continuous integrity
|
||||
|
||||
## References
|
||||
|
||||
- [Full chain verification in @veritasacta/verify](https://www.npmjs.com/package/@veritasacta/verify)
|
||||
- [Hash-chained audit trail explainer](https://veritasacta.com/docs/chains)
|
||||
- Use `/verify-receipt` for single-receipt verification
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
description: "Verify a single Ed25519-signed receipt file. Returns exit 0 if valid, 1 if tampered, 2 if malformed."
|
||||
argument-hint: "<path-to-receipt.json>"
|
||||
---
|
||||
|
||||
# Verify Receipt
|
||||
|
||||
Verify an Ed25519 signed receipt produced by `protect-mcp`. The verification
|
||||
runs entirely offline using `@veritasacta/verify` from npm. No network
|
||||
requests, no vendor lookups, no trust in the operator required.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/verify-receipt ./receipts/2026-04-15T10-30-00Z.json
|
||||
```
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Reads the receipt JSON file
|
||||
2. Validates the structure (required fields, correct types)
|
||||
3. Extracts the public key and signature
|
||||
4. Reconstructs the canonical form (JCS, RFC 8785)
|
||||
5. Verifies the Ed25519 signature over the canonical bytes
|
||||
6. Reports the result
|
||||
|
||||
## Implementation
|
||||
|
||||
Run this in the Bash tool:
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify "$1"
|
||||
```
|
||||
|
||||
Where `$1` is the receipt path provided by the user.
|
||||
|
||||
### Expected exit codes
|
||||
|
||||
| Exit | Meaning | Action |
|
||||
|------|---------|--------|
|
||||
| 0 | Valid receipt, signature verified | Report: "Verified. Receipt authentic." |
|
||||
| 1 | Signature mismatch — receipt tampered | Report: "TAMPERED. Signature does not match payload." |
|
||||
| 2 | Malformed receipt | Report: "Malformed. Missing required fields or invalid structure." |
|
||||
|
||||
## What to Show the User
|
||||
|
||||
For a valid receipt:
|
||||
|
||||
```
|
||||
Verified ✓
|
||||
|
||||
Receipt: rec_8f92a3b1
|
||||
Tool: Bash
|
||||
Decision: allow (policy: autoresearch-safe)
|
||||
Signed at: 2026-04-15T10:30:00.000Z
|
||||
Signer: 4437ca56815c0516...
|
||||
Chain link: parent=rec_3d1ab7c2 ✓
|
||||
```
|
||||
|
||||
For a tampered receipt:
|
||||
|
||||
```
|
||||
TAMPERED ✗
|
||||
|
||||
The signature does not match the payload. This receipt has been modified
|
||||
since it was signed.
|
||||
|
||||
Receipt ID: rec_8f92a3b1
|
||||
Expected signer: 4437ca56815c0516...
|
||||
|
||||
Possible causes:
|
||||
- A field was edited after signing (most common)
|
||||
- The signature was copied from a different receipt
|
||||
- The public key was replaced
|
||||
|
||||
Compare this receipt against a known-good copy to identify the altered field.
|
||||
```
|
||||
|
||||
For a malformed receipt:
|
||||
|
||||
```
|
||||
MALFORMED ✗
|
||||
|
||||
The file is not a valid Veritas Acta receipt. Missing or invalid fields:
|
||||
<list the specific structural issues>
|
||||
|
||||
A valid receipt must include: receipt_id, receipt_version, issuer_id,
|
||||
event_time, tool_name, decision, public_key, signature.
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Receipt format: [IETF draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
|
||||
- Verify CLI: [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify)
|
||||
- Chain verification: use `/audit-chain` for a full chain walk
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hook": {
|
||||
"type": "command",
|
||||
"command": "npx protect-mcp@latest evaluate --policy \"${PROTECT_MCP_POLICY:-./protect.cedar}\" --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hook": {
|
||||
"type": "command",
|
||||
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts \"${PROTECT_MCP_RECEIPTS:-./receipts/}\" --key \"${PROTECT_MCP_KEY:-./protect-mcp.key}\""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
name: protect-mcp-setup
|
||||
description: Configure Cedar policy enforcement and Ed25519 signed receipts for Claude Code tool calls. Use when setting up projects that need cryptographic audit trails, policy-gated tool execution, or compliance-ready evidence of agent actions.
|
||||
---
|
||||
|
||||
# protect-mcp — Policy Enforcement + Signed Receipts
|
||||
|
||||
Cryptographic governance for every Claude Code tool call. Each invocation is
|
||||
evaluated against a Cedar policy and produces an Ed25519-signed receipt that
|
||||
anyone can verify offline.
|
||||
|
||||
## Overview
|
||||
|
||||
Claude Code runs powerful tools: `Bash`, `Edit`, `Write`, `WebFetch`. By default
|
||||
there is no audit trail, no policy enforcement, and no way to prove what was
|
||||
decided after the fact. `protect-mcp` closes all three gaps:
|
||||
|
||||
- **Cedar policies** (AWS's open authorization engine) evaluate every tool call
|
||||
before execution. Cedar deny is authoritative.
|
||||
- **Ed25519 receipts** record each decision with its inputs, the policy that
|
||||
governed it, and the outcome. Receipts are hash-chained.
|
||||
- **Offline verification** via `npx @veritasacta/verify`. No server, no account,
|
||||
no trust in the operator.
|
||||
|
||||
## Problem
|
||||
|
||||
AI agents make decisions that affect money, safety, and rights. The Claude Code
|
||||
session log records what happened, but the log is:
|
||||
|
||||
- Mutable — anyone with access can edit it
|
||||
- Unsigned — there is no way to prove integrity
|
||||
- Operator-bound — verification requires trusting whoever holds the log
|
||||
|
||||
For compliance contexts (finance, healthcare, regulated research), this is not
|
||||
sufficient. You need tamper-evident evidence that can be verified by third
|
||||
parties without trusting you.
|
||||
|
||||
## Solution
|
||||
|
||||
Add `protect-mcp` to your Claude Code project:
|
||||
|
||||
```bash
|
||||
# 1. Install the plugin (adds hooks + skill to your project)
|
||||
claude plugin install wshobson/agents/protect-mcp
|
||||
|
||||
# 2. Configure hooks in .claude/settings.json (see below)
|
||||
|
||||
# 3. Start the receipt-signing server (runs locally, no external calls)
|
||||
npx protect-mcp@latest serve --enforce
|
||||
|
||||
# 4. Use Claude Code normally. Every tool call is now policy-evaluated
|
||||
# and produces a signed receipt in ./receipts/
|
||||
```
|
||||
|
||||
## Hook Configuration
|
||||
|
||||
Add the following to your project's `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hook": {
|
||||
"type": "command",
|
||||
"command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" || exit 2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hook": {
|
||||
"type": "command",
|
||||
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### What each hook does
|
||||
|
||||
**PreToolUse** — Runs BEFORE the tool executes. Evaluates the tool call against
|
||||
your Cedar policy file. If Cedar returns `deny`, the hook exits with code 2 and
|
||||
Claude Code blocks the tool call entirely.
|
||||
|
||||
**PostToolUse** — Runs AFTER the tool completes. Signs a receipt containing the
|
||||
tool name, input hash, output hash, decision, policy digest, and timestamp.
|
||||
Writes the receipt to `./receipts/<timestamp>.json`.
|
||||
|
||||
## Cedar Policy File
|
||||
|
||||
Create `./protect.cedar` at the project root:
|
||||
|
||||
```cedar
|
||||
// Allow read-only tools by default
|
||||
permit (
|
||||
principal,
|
||||
action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebFetch"],
|
||||
resource
|
||||
);
|
||||
|
||||
// Require explicit allow for destructive tools
|
||||
permit (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
// Allow safe commands only
|
||||
context.command_pattern in ["git", "npm", "ls", "cat", "echo", "pwd", "test"]
|
||||
};
|
||||
|
||||
// Never allow recursive deletion
|
||||
forbid (
|
||||
principal,
|
||||
action == Action::"Bash",
|
||||
resource
|
||||
) when {
|
||||
context.command_pattern == "rm -rf"
|
||||
};
|
||||
|
||||
// Require confirmation for writes outside the project
|
||||
forbid (
|
||||
principal,
|
||||
action in [Action::"Edit", Action::"Write"],
|
||||
resource
|
||||
) when {
|
||||
context.path_starts_with != "."
|
||||
};
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Verify a single receipt:
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify receipts/2026-04-15T10-30-00Z.json
|
||||
# Exit 0 = valid
|
||||
# Exit 1 = tampered
|
||||
# Exit 2 = malformed
|
||||
```
|
||||
|
||||
Verify the entire chain:
|
||||
|
||||
```bash
|
||||
npx @veritasacta/verify receipts/*.json
|
||||
```
|
||||
|
||||
Use the plugin's slash commands from within Claude Code:
|
||||
|
||||
```
|
||||
/verify-receipt receipts/latest.json
|
||||
/audit-chain ./receipts/ --last 20
|
||||
```
|
||||
|
||||
## Receipt Format
|
||||
|
||||
Each receipt is a JSON file with this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"receipt_id": "rec_8f92a3b1",
|
||||
"receipt_version": "1.0",
|
||||
"issuer_id": "claude-code-protect-mcp",
|
||||
"event_time": "2026-04-15T10:30:00.000Z",
|
||||
"tool_name": "Bash",
|
||||
"input_hash": "sha256:a3f8...",
|
||||
"decision": "allow",
|
||||
"policy_id": "autoresearch-safe",
|
||||
"policy_digest": "sha256:b7e2...",
|
||||
"parent_receipt_id": "rec_3d1ab7c2",
|
||||
"public_key": "4437ca56815c0516...",
|
||||
"signature": "4cde814b7889e987..."
|
||||
}
|
||||
```
|
||||
|
||||
- **Ed25519** signatures (RFC 8032)
|
||||
- **JCS canonicalization** (RFC 8785) before signing
|
||||
- **Hash-chained** to the previous receipt via `parent_receipt_id`
|
||||
- **Offline verifiable** — no network call, no vendor lookup
|
||||
|
||||
## Why This Matters
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| "Trust me, the agent only read files" | Cryptographically provable: every Read logged and signed |
|
||||
| "The log shows it happened" | The receipt proves it happened, and no one can edit it |
|
||||
| "You'd have to audit our system" | Anyone can verify every receipt offline |
|
||||
| "Logs might be different by now" | Ed25519 signatures lock the record at signing time |
|
||||
|
||||
## Standards
|
||||
|
||||
- **Ed25519** — RFC 8032 (digital signatures)
|
||||
- **JCS** — RFC 8785 (deterministic JSON canonicalization)
|
||||
- **Cedar** — AWS's open authorization policy language
|
||||
- **IETF draft** — [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
|
||||
|
||||
## Related
|
||||
|
||||
- **npm**: [protect-mcp](https://www.npmjs.com/package/protect-mcp) (v0.5.5, 10K+ monthly downloads)
|
||||
- **Verify CLI**: [@veritasacta/verify](https://www.npmjs.com/package/@veritasacta/verify)
|
||||
- **Source**: [github.com/ScopeBlind/scopeblind-gateway](https://github.com/ScopeBlind/scopeblind-gateway)
|
||||
- **Protocol**: [veritasacta.com](https://veritasacta.com)
|
||||
- **Integrations**: Microsoft Agent Governance Toolkit (PR #667), AWS cedar-policy/cedar-for-agents (PR #64)
|
||||
Reference in New Issue
Block a user