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:
tommylauren
2026-04-16 12:58:55 -04:00
committed by Seth Hobson
parent 8861878dc1
commit d3f25db226
9 changed files with 979 additions and 0 deletions
+14
View File
@@ -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"
}
+161
View File
@@ -0,0 +1,161 @@
# protect-mcp
Cedar policy enforcement + Ed25519 signed receipts for every Claude Code tool call.
[![npm version](https://img.shields.io/npm/v/protect-mcp)](https://www.npmjs.com/package/protect-mcp)
[![Downloads](https://img.shields.io/npm/dm/protect-mcp)](https://www.npmjs.com/package/protect-mcp)
[![License](https://img.shields.io/badge/license-MIT-blue)](./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)
+136
View File
@@ -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
+22
View File
@@ -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)