Skills · Security

Signed Audit Trails for Claude Code Tool Calls

Unverified30/40

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.

Originally by wshobson · MIT

Claude CodePartialHas SKILL.md but declares no allowed-tools — Claude Code will ask for permission each time
Cursor·UnknownWe have not crawled the repo tree, so we will not guess
Codex·UnknownWe have not crawled the repo tree, so we will not guess
Gemini CLI·UnknownThe spec defines no detection rule for Gemini
Copilot·UnknownWe have not crawled the repo tree, so we will not guess
npx agentalley add signed-audit-trails-recipe

This command does not work yet — the CLI is still being built. Until then, use Raw in the reader below to take the file.

Who is stuck, and on what

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.

The whole source

No sign-in, no blur, nothing truncated
signed-audit-trails-recipe/SKILL.md346 lines11.3 KBRawView on GitHub
Frontmatter — 2 properties
namesigned-audit-trails-recipe
descriptionStep-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.
1---
2name: signed-audit-trails-recipe
3description: 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.
4---A5No allowed-tools declared — no way to tell what this skill may touch
5 
6# Signed Audit Trails for Claude Code Tool Calls
7 
8Cookbook-style walkthrough for cryptographically signed receipts on every
9Claude Code tool call. This is the teaching skill. For the runtime
10implementation, install the [`protect-mcp`](../../protect-mcp/) plugin.
11 
12## What this gives you
13 
14Every tool call (`Bash`, `Edit`, `Write`, `WebFetch`) is:A4This skill pulls in web or user content but never says to treat that content as data. A signal, not proof.
15 
161. **Evaluated against a Cedar policy** before execution. If the policy denies
17 the call, the tool does not run.
182. **Signed as an Ed25519 receipt** after execution. Receipts are
19 JCS-canonical, hash-chained, and verifiable offline by anyone with the
20 public key.
21 
22An auditor, regulator, or counterparty can verify the full chain later with a
23single CLI command (`npx @veritasacta/verify receipts/*.json`). No network
24call, no vendor lookup, no trust in the operator.
25 
26## When to use the pattern
27 
28- **Regulated environments** (finance, healthcare, critical infrastructure)
29 where you need tamper-evident evidence of agent behavior
30- **CI/CD pipelines** where you want to prove that a policy gate held for
31 every automated build step
32- **Multi-party collaboration** where a counterparty wants to verify your
33 agent's behavior without trusting your operator
34- **Compliance contexts** (EU AI Act Article 12, SLSA provenance for
35 agent-built software) where standard logging is not sufficient
36 
37## Step 1: Install the hook configuration
38 
39Create `.claude/settings.json` in your project root:
40 
41```json
42{
43 "hooks": {
44 "PreToolUse": [
45 {
46 "matcher": ".*",
47 "hook": {
48 "type": "command",
49 "command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
50 }
51 }
52 ],
53 "PostToolUse": [
54 {
55 "matcher": ".*",
56 "hook": {
57 "type": "command",
58 "command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/ --key ./protect-mcp.key"
59 }
60 }
61 ]
62 }
63}
64```
65 
66The first run of `protect-mcp sign` generates `./protect-mcp.key` (Ed25519
67private key) if one does not exist. Commit the **public** key fingerprint
68(visible in any receipt's `public_key` field); do not commit the private
69key.
70 
71Add the private key and receipt directory to `.gitignore`:
72 
73```bash
74echo "./protect-mcp.key" >> .gitignore
75echo "./receipts/" >> .gitignore
76```
77 
78## Step 2: Write a Cedar policy
79 
80Create `./protect.cedar`:
81 
82```cedar
83// Allow all read-oriented tools by default.
84permit (
85 principal,
86 action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebSearch"],
87 resource
88);
89 
90// Allow Bash commands from a safe list only.
91permit (
92 principal,
93 action == Action::"Bash",
94 resource
95) when {
96 context.command_pattern in [
97 "git", "npm", "pnpm", "yarn", "ls", "cat", "pwd",
98 "echo", "test", "node", "python", "make"
99 ]
100};
101 
102// Explicit deny on destructive commands. Cedar deny is authoritative.
103forbid (
104 principal,
105 action == Action::"Bash",
106 resource
107) when {
108 context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
109};
110 
111// Restrict writes to the project directory.
112permit (
113 principal,
114 action in [Action::"Write", Action::"Edit"],
115 resource
116) when {
117 context.path_starts_with == "./"
118};
119```
120 
121Four rules:
122 
123- Read-oriented tools always allowed
124- `Bash` allowed for safe command patterns (`git`, `npm`, etc.)
125- `Bash rm -rf` and similar destructive commands explicitly denied
126- Writes allowed only within the project (`./` prefix)
127 
128Cedar `forbid` rules take precedence over `permit` rules, so destructive
129commands cannot be bypassed by a later permissive rule.
130 
131## Step 3: Use Claude Code normally
132 
133Start Claude Code. Every tool call goes through both hooks:
134 
135```
136You: Please read the README and summarize it.
137 
138Claude: I will read README.md.
139 [PreToolUse: Read ./README.md -> allow]
140 [Tool: Read executes]
141 [PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/]
142 
143... summary of README ...
144```
145 
146A session of 20 tool calls produces 20 receipts, each hash-chained to its
147predecessor.
148 
149## Step 4: Inspect a receipt
150 
151```bash
152cat ./receipts/$(ls -t ./receipts/ | head -1)
153```
154 
155```json
156{
157 "receipt_id": "rcpt-a8f3c9d2",
158 "receipt_version": "1.0",
159 "issuer_id": "claude-code-protect-mcp",
160 "event_time": "2026-04-17T12:34:56.123Z",
161 "tool_name": "Read",
162 "input_hash": "sha256:a3f8c9d2e1b7465f...",
163 "decision": "allow",
164 "policy_id": "protect.cedar",
165 "policy_digest": "sha256:b7e2f4a6c8d0e1f3...",
166 "parent_receipt_id": "rcpt-3d1ab7c2",
167 "public_key": "4437ca56815c0516...",
168 "signature": "4cde814b7889e987..."
169}
170```
171 
172Every field except `signature` and `public_key` is covered by the Ed25519
173signature. Modifying any field after signing invalidates the signature.
174 
175## Step 5: Verify the receipt chain
176 
177```bash
178npx @veritasacta/verify ./receipts/*.json
179```
180 
181Exit codes:
182 
183| Code | Meaning |
184|------|---------|
185| `0` | All receipts verified; chain intact |
186| `1` | A receipt failed signature verification (tampered, or wrong key) |
187| `2` | A receipt was malformed |
188 
189## Step 6: Demonstrate tamper detection
190 
191Modify any receipt's `decision` field from `allow` to `deny`:
192 
193```bash
194python3 -c "
195import json, os
196path = './receipts/' + sorted(os.listdir('./receipts'))[-1]
197r = json.loads(open(path).read())
198r['decision'] = 'deny'
199open(path, 'w').write(json.dumps(r))
200"
201 
202npx @veritasacta/verify ./receipts/*.json
203```
204 
205The verifier exits with code `1` and reports which receipt failed. The
206Ed25519 signature no longer matches the JCS-canonical bytes of the
207tampered payload.
208 
209Restore the field and verification passes again.
210 
211## How the cryptography works
212 
213Three invariants make receipts verifiable offline across any conformant
214implementation:
215 
2161. **JCS canonicalization (RFC 8785)** before signing. Keys sorted,
217 whitespace minimized, strings NFC-normalized. Two independent
218 implementations produce byte-identical signing payloads for the same
219 receipt content.
2202. **Ed25519 signatures (RFC 8032)** over the canonical bytes.
221 Deterministic, fixed-size, no nonce dependency.
2223. **Hash chain linkage.** Each receipt's `parent_receipt_hash` is the
223 SHA-256 of the predecessor's canonical form. Insertions, deletions, and
224 reorderings break later receipts.
225 
226For the formal wire format see
227[draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/).
228 
229## Cross-implementation interop
230 
231The receipt format has four independent implementations today:
232 
233| Implementation | Language | Use case |
234|----------------|----------|----------|
235| [protect-mcp](https://www.npmjs.com/package/protect-mcp) | TypeScript | Claude Code, Cursor, MCP hosts |
236| [protect-mcp-adk](https://pypi.org/project/protect-mcp-adk/) | Python | Google Agent Development Kit |
237| [sb-runtime](https://github.com/ScopeBlind/sb-runtime) | Rust | OS-level sandbox (Landlock + seccomp) |
238| APS governance hook | Python | CrewAI, LangChain |
239 
240A receipt produced by any of them verifies against
241[`@veritasacta/verify`](https://www.npmjs.com/package/@veritasacta/verify).
242The auditor does not need to trust the operator's tooling choice: the format
243is the contract.
244 
245## CI/CD integration
246 
247Gate merges on receipt chain verification so no build lands with a broken
248evidence chain:
249 
250```yaml
251# .github/workflows/verify-receipts.yml
252name: Verify Decision Receipts
253on: [push, pull_request]
254 
255jobs:
256 verify:
257 runs-on: ubuntu-latest
258 steps:
259 - uses: actions/checkout@v4
260 - uses: actions/setup-node@v4
261 with: { node-version: '20' }
262 - name: Run governed agent
263 run: python scripts/run_agent.py > receipts.jsonl
264 - name: Verify receipt chain
265 run: npx @veritasacta/verify receipts.jsonl
266```
267 
268Archive the receipts as an artifact so the chain survives beyond the job run:
269 
270```yaml
271 - name: Upload receipts
272 if: always()
273 uses: actions/upload-artifact@v4
274 with:
275 name: decision-receipts
276 path: receipts/
277```
278 
279## Composition with SLSA provenance for agent-built software
280 
281When Claude Code builds and releases software (running `npm install`,
282`npm build`, `npm publish` as tool calls), the receipt chain is the
283per-step build log. SLSA Provenance v1 has an extension point for this: the
284`byproducts` field can reference the receipt chain alongside the build
285attestation.
286 
287The [agent-commit build type](https://refs.arewm.com/agent-commit/v0.2)
288documents the pattern using the ResourceDescriptor shape:
289 
290```json
291{
292 "name": "decision-receipts",
293 "digest": { "sha256": "..." },
294 "uri": "oci://registry/org/build-xyz/receipts:sha256-...",
295 "annotations": {
296 "predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1",
297 "signerRole": "supervisor-hook"
298 }
299}
300```
301 
302The SLSA provenance is signed by the builder identity; the receipt
303attestation is signed by the supervisor-hook identity. Two trust domains,
304cross-referenced at the byproduct layer. See
305[slsa-framework/slsa#1594](https://github.com/slsa-framework/slsa/issues/1594)
306for the composition discussion.
307 
308## Common pitfalls
309 
310**Private key in version control.** The generated `./protect-mcp.key` must
311not be committed. The examples above add it to `.gitignore`. If a key is
312accidentally committed, rotate immediately (delete the key file and let the
313hook regenerate on next run).
314 
315**Hook command quoting.** The hooks receive `$TOOL_NAME` and `$TOOL_INPUT`
316as environment variables. Keep the quoting `"$TOOL_INPUT"` so inputs with
317spaces or special characters pass through intact.
318 
319**Receipts directory in CI.** If Claude Code runs in CI, upload receipts as
320an artifact at the end of the job or the chain is lost at job end.
321 
322**Policy is missing.** The example `PreToolUse` hook uses
323`--fail-on-missing-policy false` so an absent `./protect.cedar` does not
324break Claude Code out of the box. Remove this flag in production so a
325missing policy is treated as a hard failure.
326 
327## Related in this marketplace
328 
329- [`protect-mcp`](../../protect-mcp/) — the runtime hook implementation
330 (use this plugin in production)
331- [`review-agent-governance`](../../review-agent-governance/) — require
332 human approval before review-surface actions; composes with protect-mcp
333 
334## References
335 
336- [`draft-farley-acta-signed-receipts`](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — IETF draft, receipt wire format
337- [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) — Ed25519
338- [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) — JCS
339- [Cedar policy language](https://docs.cedarpolicy.com/)
340- [protect-mcp on npm](https://www.npmjs.com/package/protect-mcp)
341- [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify)
342- [in-toto/attestation#549](https://github.com/in-toto/attestation/pull/549) — Decision Receipt predicate proposal
343- [agent-commit build type](https://refs.arewm.com/agent-commit/v0.2) — SLSA provenance for agent-produced commits
344- [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) (`examples/protect-mcp-governed/`)
345- [AWS Cedar for Agents](https://github.com/cedar-policy/cedar-for-agents)
346 

Reviews

Installed this one?Write the first review and take the Trailblazer badge.

Reviews only open after a real install, so this is empty — and we leave it empty rather than invent one.

Alternatives

Also in Security