/signed-audit-trails-recipe
Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification,
Claude Code
$ npx -y skills add wshobson/agents --skill signed-audit-trails-recipe --agent claude-code
Codex
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a codex -y
Cursor
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a cursor -y
Gemini CLI
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a gemini-cli -y
GitHub Copilot
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a github-copilot -y
OpenCode
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a opencode -y
Windsurf
$ npx skills add wshobson/agents --skill signed-audit-trails-recipe -a windsurf -y
SKILL.md
signed-audit-trails-recipe.SKILL.mdname: signed-audit-trails-recipe
description: Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification, tamper detection, CI/CD integration, and SLSA composition.
Signed Audit Trails for Claude Code Tool Calls
Cookbook-style walkthrough for cryptographically signed receipts on every Claude Code tool call. This is the teaching skill. For the runtime implementation, install the [`protect-mcp`](../../../protect-mcp/) plugin.
What this gives you
Every tool call (`Bash`, `Edit`, `Write`, `WebFetch`) is:
1. **Evaluated against a Cedar policy** before execution. If the policy denies the call, the tool does not run. 2. **Signed as an Ed25519 receipt** after execution. Receipts are JCS-canonical and verifiable offline by anyone with the public key.
An auditor, regulator, or counterparty can verify every receipt later (Step 5). No network call, no vendor lookup, no trust in the operator.
When to use the pattern
- **Regulated environments** (finance, healthcare, critical infrastructure)
where you need tamper-evident evidence of agent behavior
- **CI/CD pipelines** where you want to prove that a policy gate held for
every automated build step
- **Multi-party collaboration** where a counterparty wants to verify your
agent's behavior without trusting your operator
- **Compliance contexts** (EU AI Act Article 12, SLSA provenance for
agent-built software) where standard logging is not sufficient
Step 1: Install the hook configuration
Install the `protect-mcp` plugin with `/plugin install protect-mcp`. Its hooks run `evaluate.sh` before each tool call and `sign.sh` after it. Both scripts read the hook event from stdin, because Claude Code sets no `TOOL_NAME` or `TOOL_INPUT` variables. See [`references/hook-wiring.md`](references/hook-wiring.md) for the hook configuration and what each script passes to protect-mcp.
protect-mcp 0.7.4 does not create the signing key, and without a key the receipts are unsigned. Create `./protect-mcp.key` once. The command never replaces an existing key:
if [ ! -e ./protect-mcp.key ]; then
d=$(mktemp -d) && npx protect-mcp@0.32.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key
fi
chmod 600 ./protect-mcp.key
Give auditors the `publicKey` value from that file. Do not commit the file, because it also holds the private key.
Add the private key and receipt directory to `.gitignore`:
echo "/protect-mcp.key" >> .gitignore
echo "/receipts/" >> .gitignore
Step 2: Write a Cedar policy
Create `./protect.cedar` from the example in [`references/cedar-policy.md`](references/cedar-policy.md). It allows read-only tools and a short list of Bash commands, denies shell chaining and destructive commands, and limits writes to the project with `..` segments denied.
Step 3: Use Claude Code normally
Start Claude Code. Every tool call goes through both hooks:
You: Please read the README and summarize it.
Claude: I will read README.md.
[PreToolUse: Read ./README.md -> allow]
[Tool: Read executes]
[PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/]
... summary of README ...
A session of 20 tool calls appends 20 receipts to `./receipts/receipts.jsonl`.
Step 4: Inspect a receipt
protect-mcp 0.7.4 appends each receipt as one line of `./receipts/receipts.jsonl`. Print the newest one:
tail -n 1 ./receipts/receipts.jsonl | python3 -m json.tool
The receipt is a signed v2 envelope that names the tool, and it holds no public key. See [`references/receipt-format.md`](references/receipt-format.md) for a sample and the signed fields.
Step 5: Verify the receipts
Pass the `publicKey` value from `./protect-mcp.key` to the verifier:
PUB=$(node -p 'JSON.parse(require("fs").readFileSync("./protect-mcp.key")).publicKey')
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"Exit codes:
| Code | Meaning | |------|---------| | `0` | Every receipt verified | | `1` | A receipt failed verification (tampered, wrong key, or malformed line) | | `2` | The receipts file could not be read |
Step 6: Demonstrate tamper detection
Change the newest receipt's `decision` from `allow` to `deny`:
python3 -c "
import json
path = './receipts/receipts.jsonl'
lines = open(path).read().splitlines()
r = json.loads(lines[-1])
r['payload']['decision'] = 'deny'
lines[-1] = json.dumps(r)
open(path, 'w').write('\n'.join(lines) + '\n')
"
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"The verifier exits with code `1` and reports which line failed. The Ed25519 signature no longer matches the JCS-canonical bytes of the tampered payload.
Restore the field and verification passes again.
How the cryptography works
Two invariants make receipts verifiable offline across any conformant implementation:
1. **JCS canonicalization (RFC 8785)** before signing. Keys sorted by UTF-16 code units, whitespace minimized, strings not normalized. Two independent implementations produce byte-identical signing payloads for the same receipt content. 2. **Ed25519 signatures (RFC 8032)** over the canonical bytes. Deterministic, fixed-size, no nonce dependency.
protect-mcp 0.7.4 receipts carry no link to the previous receipt, so a deleted receipt goes undetected.
For the formal wire format see [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/).
Cross-implementation interop
The receipt format has four independent implementations today:
| Implementation | Language | Use case | |----------------|----------|----------| | [protect-mcp](https://www.npmjs.com/package/protect-mcp) | TypeScript | Claude Code, Cursor, MCP hosts | | [protect-mcp-
Read more
name: signed-audit-trails-recipe description: Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification, tamper detection, CI/CD integration, and SLSA composition.
Signed Audit Trails for Claude Code Tool Calls
Cookbook-style walkthrough for cryptographically signed receipts on every Claude Code tool call. This is the teaching skill. For the runtime implementation, install the [`protect-mcp`](../../../protect-mcp/) plugin.
What this gives you
Every tool call (`Bash`, `Edit`, `Write`, `WebFetch`) is:
1. **Evaluated against a Cedar policy** before execution. If the policy denies the call, the tool does not run. 2. **Signed as an Ed25519 receipt** after execution. Receipts are JCS-canonical and verifiable offline by anyone with the public key.
An auditor, regulator, or counterparty can verify every receipt later (Step 5). No network call, no vendor lookup, no trust in the operator.
When to use the pattern
- **Regulated environments** (finance, healthcare, critical infrastructure)
where you need tamper-evident evidence of agent behavior
- **CI/CD pipelines** where you want to prove that a policy gate held for
every automated build step
- **Multi-party collaboration** where a counterparty wants to verify your
agent's behavior without trusting your operator
- **Compliance contexts** (EU AI Act Article 12, SLSA provenance for
agent-built software) where standard logging is not sufficient
Step 1: Install the hook configuration
Install the `protect-mcp` plugin with `/plugin install protect-mcp`. Its hooks run `evaluate.sh` before each tool call and `sign.sh` after it. Both scripts read the hook event from stdin, because Claude Code sets no `TOOL_NAME` or `TOOL_INPUT` variables. See [`references/hook-wiring.md`](references/hook-wiring.md) for the hook configuration and what each script passes to protect-mcp.
protect-mcp 0.7.4 does not create the signing key, and without a key the receipts are unsigned. Create `./protect-mcp.key` once. The command never replaces an existing key:
if [ ! -e ./protect-mcp.key ]; then d=$(mktemp -d) && npx protect-mcp@0.32.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key fi chmod 600 ./protect-mcp.key
Give auditors the `publicKey` value from that file. Do not commit the file, because it also holds the private key.
Add the private key and receipt directory to `.gitignore`:
echo "/protect-mcp.key" >> .gitignore echo "/receipts/" >> .gitignore
Step 2: Write a Cedar policy
Create `./protect.cedar` from the example in [`references/cedar-policy.md`](references/cedar-policy.md). It allows read-only tools and a short list of Bash commands, denies shell chaining and destructive commands, and limits writes to the project with `..` segments denied.
Step 3: Use Claude Code normally
Start Claude Code. Every tool call goes through both hooks:
You: Please read the README and summarize it. Claude: I will read README.md. [PreToolUse: Read ./README.md -> allow] [Tool: Read executes] [PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/] ... summary of README ...
A session of 20 tool calls appends 20 receipts to `./receipts/receipts.jsonl`.
Step 4: Inspect a receipt
protect-mcp 0.7.4 appends each receipt as one line of `./receipts/receipts.jsonl`. Print the newest one:
tail -n 1 ./receipts/receipts.jsonl | python3 -m json.tool
The receipt is a signed v2 envelope that names the tool, and it holds no public key. See [`references/receipt-format.md`](references/receipt-format.md) for a sample and the signed fields.
Step 5: Verify the receipts
Pass the `publicKey` value from `./protect-mcp.key` to the verifier:
PUB=$(node -p 'JSON.parse(require("fs").readFileSync("./protect-mcp.key")).publicKey')
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"Exit codes:
| Code | Meaning | |------|---------| | `0` | Every receipt verified | | `1` | A receipt failed verification (tampered, wrong key, or malformed line) | | `2` | The receipts file could not be read |
Step 6: Demonstrate tamper detection
Change the newest receipt's `decision` from `allow` to `deny`:
python3 -c "
import json
path = './receipts/receipts.jsonl'
lines = open(path).read().splitlines()
r = json.loads(lines[-1])
r['payload']['decision'] = 'deny'
lines[-1] = json.dumps(r)
open(path, 'w').write('\n'.join(lines) + '\n')
"
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"The verifier exits with code `1` and reports which line failed. The Ed25519 signature no longer matches the JCS-canonical bytes of the tampered payload.
Restore the field and verification passes again.
How the cryptography works
Two invariants make receipts verifiable offline across any conformant implementation:
1. **JCS canonicalization (RFC 8785)** before signing. Keys sorted by UTF-16 code units, whitespace minimized, strings not normalized. Two independent implementations produce byte-identical signing payloads for the same receipt content. 2. **Ed25519 signatures (RFC 8032)** over the canonical bytes. Deterministic, fixed-size, no nonce dependency.
protect-mcp 0.7.4 receipts carry no link to the previous receipt, so a deleted receipt goes undetected.
For the formal wire format see [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/).
Cross-implementation interop
The receipt format has four independent implementations today:
| Implementation | Language | Use case | |----------------|----------|----------| | [protect-mcp](https://www.npmjs.com/package/protect-mcp) | TypeScript | Claude Code, Cursor, MCP hosts | | [protect-mcp-
Use one collection of plugins, agents, skills, and commands across Claude Code, OpenAI Codex CLI, Cursor, OpenCode, Antigravity CLI, GitHub Copilot, and Pi. A harness is the coding tool that loads and runs the components.
Other skills on agents.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about signed-audit-trails-recipe.
- Which coding agents can use signed-audit-trails-recipe?
- signed-audit-trails-recipe installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

